diff --git a/.github/workflows/lint-toolchain.yml b/.github/workflows/lint-toolchain.yml index 45b3604ed..731c883cc 100644 --- a/.github/workflows/lint-toolchain.yml +++ b/.github/workflows/lint-toolchain.yml @@ -11,6 +11,11 @@ jobs: - uses: actions/checkout@v4 - name: MFC Python setup + uses: actions/setup-python@v5 + with: + python-version: '3.14' + + - name: Initialize MFC run: ./mfc.sh init - name: Lint the toolchain diff --git a/src/common/m_checker_common.fpp b/src/common/m_checker_common.fpp index 1ec314536..4d5b7d80c 100644 --- a/src/common/m_checker_common.fpp +++ b/src/common/m_checker_common.fpp @@ -26,72 +26,14 @@ contains !! Used by all three stages impure subroutine s_check_inputs_common -#ifndef MFC_PRE_PROCESS - call s_check_inputs_time_stepping - call s_check_inputs_finite_difference -#endif - #ifndef MFC_SIMULATION call s_check_total_cells #endif - ! Run by all three stages - call s_check_inputs_simulation_domain - call s_check_inputs_model_eqns_and_num_fluids - if (igr) then - call s_check_inputs_igr - else -#ifndef MFC_POST_PROCESS - call s_check_inputs_bubbles_euler - call s_check_inputs_qbmm_and_polydisperse - call s_check_inputs_adv_n - call s_check_inputs_hypoelasticity - call s_check_inputs_phase_change - call s_check_inputs_ibm -#endif - if (recon_type == WENO_TYPE) then - call s_check_inputs_weno - elseif (recon_type == MUSCL_TYPE) then - call s_check_inputs_muscl - end if - call s_check_inputs_surface_tension - call s_check_inputs_mhd - end if - call s_check_inputs_bc - call s_check_inputs_stiffened_eos - call s_check_inputs_moving_bc - end subroutine s_check_inputs_common -#ifndef MFC_PRE_PROCESS - - !> Checks constraints on the time-stepping parameters. - !! Called by s_check_inputs_common for simulation and post-processing - impure subroutine s_check_inputs_time_stepping - if (cfl_dt) then - @:PROHIBIT(cfl_target < 0 .or. cfl_target > 1._wp) - @:PROHIBIT(t_stop <= 0) - @:PROHIBIT(t_save <= 0) - @:PROHIBIT(t_save > t_stop) - @:PROHIBIT(n_start < 0) - else - @:PROHIBIT(t_step_start < 0) - @:PROHIBIT(t_step_stop <= t_step_start) - @:PROHIBIT(t_step_save > t_step_stop - t_step_start) - end if - end subroutine s_check_inputs_time_stepping - - !> Checks constraints on the finite difference parameters. - !! Called by s_check_inputs_common for simulation and post-processing - impure subroutine s_check_inputs_finite_difference - @:PROHIBIT(all(fd_order /= (/dflt_int, 1, 2, 4/)), "fd_order must be 1, 2, or 4") - end subroutine s_check_inputs_finite_difference - -#endif - #ifndef MFC_SIMULATION - ! Checks constraints on the total number of cells impure subroutine s_check_total_cells character(len=18) :: numStr !< for int to string conversion integer(kind=8) :: min_cells @@ -108,318 +50,6 @@ contains #ifndef MFC_POST_PROCESS - !> Checks constraints on the bubble parameters. - !! Called by s_check_inputs_common for pre-processing and simulation - impure subroutine s_check_inputs_bubbles_euler - @:PROHIBIT(bubbles_euler .and. nb < 1, "The Ensemble-Averaged Bubble Model requires nb >= 1") - @:PROHIBIT(bubbles_euler .and. polydisperse .and. (nb == 1), "Polydisperse bubble dynamics requires nb > 1") - @:PROHIBIT(bubbles_euler .and. polydisperse .and. (mod(nb, 2) == 0), "nb must be odd") - @:PROHIBIT(bubbles_euler .and. (.not. polytropic) .and. f_is_default(R0ref), "R0ref must be set if using bubbles_euler with polytropic = .false.") - @:PROHIBIT(bubbles_euler .and. nb == dflt_int, "nb must be set if using bubbles_euler") - @:PROHIBIT(bubbles_euler .and. thermal > 3) - @:PROHIBIT(bubbles_euler .and. model_eqns == 3, "Bubble models untested with 6-equation model (model_eqns = 3)") - @:PROHIBIT(bubbles_euler .and. model_eqns == 1, "Bubble models untested with pi-gamma model (model_eqns = 1)") - @:PROHIBIT(bubbles_euler .and. model_eqns == 4 .and. f_is_default(rhoref), "rhoref must be set if using bubbles_euler with model_eqns = 4") - @:PROHIBIT(bubbles_euler .and. model_eqns == 4 .and. f_is_default(pref), "pref must be set if using bubbles_euler with model_eqns = 4") - @:PROHIBIT(bubbles_euler .and. model_eqns == 4 .and. num_fluids /= 1, "4-equation model (model_eqns = 4) is single-component and requires num_fluids = 1") - @:PROHIBIT(bubbles_euler .and. cyl_coord, "Bubble models untested in cylindrical coordinates") - end subroutine s_check_inputs_bubbles_euler - - !> Checks constraints on the QBMM and polydisperse bubble parameters. - !! Called by s_check_inputs_common for pre-processing and simulation - impure subroutine s_check_inputs_qbmm_and_polydisperse - @:PROHIBIT(polydisperse .and. (.not. bubbles_euler), "Polydisperse bubble modeling requires the bubbles_euler flag to be set") - @:PROHIBIT(polydisperse .and. f_is_default(poly_sigma), "Polydisperse bubble modeling requires poly_sigma to be set") - @:PROHIBIT(polydisperse .and. poly_sigma <= 0) - @:PROHIBIT(qbmm .and. (.not. bubbles_euler), "QBMM requires the bubbles_euler flag to be set") - @:PROHIBIT(qbmm .and. nnode /= 4) - end subroutine s_check_inputs_qbmm_and_polydisperse - - !> Checks constraints on the adv_n flag. - !! Called by s_check_inputs_common for pre-processing and simulation - impure subroutine s_check_inputs_adv_n - @:PROHIBIT(adv_n .and. (.not. bubbles_euler)) - @:PROHIBIT(adv_n .and. num_fluids /= 1) - @:PROHIBIT(adv_n .and. qbmm) - end subroutine s_check_inputs_adv_n - - !> Checks constraints on the hypoelasticity parameters. - !! Called by s_check_inputs_common for pre-processing and simulation - impure subroutine s_check_inputs_hypoelasticity - @:PROHIBIT(hypoelasticity .and. model_eqns /= 2) -#ifdef MFC_SIMULATION - @:PROHIBIT(elasticity .and. fd_order /= 4) -#endif - end subroutine s_check_inputs_hypoelasticity - - !> Checks constraints on the hyperelasticity parameters. - !! Called by s_check_inputs_common for pre-processing and simulation - impure subroutine s_check_inputs_hyperelasticity - @:PROHIBIT(hyperelasticity .and. model_eqns == 1) - @:PROHIBIT(hyperelasticity .and. model_eqns > 3) -#ifdef MFC_SIMULATION - @:PROHIBIT(elasticity .and. fd_order /= 4) #endif - end subroutine s_check_inputs_hyperelasticity - - !> Checks constraints on the phase change parameters. - !! Called by s_check_inputs_common for pre-processing and simulation - impure subroutine s_check_inputs_phase_change - @:PROHIBIT(relax .and. model_eqns /= 3, "phase change requires model_eqns = 3") - @:PROHIBIT(relax .and. relax_model < 0, "relax_model must be in between 0 and 6") - @:PROHIBIT(relax .and. relax_model > 6, "relax_model must be in between 0 and 6") - @:PROHIBIT(relax .and. palpha_eps <= 0._wp, "palpha_eps must be positive") - @:PROHIBIT(relax .and. palpha_eps >= 1._wp, "palpha_eps must be less than 1") - @:PROHIBIT(relax .and. ptgalpha_eps <= 0._wp, "ptgalpha_eps must be positive") - @:PROHIBIT(relax .and. ptgalpha_eps >= 1._wp, "ptgalpha_eps must be less than 1") - @:PROHIBIT((.not. relax) .and. & - ((relax_model /= dflt_int) .or. (.not. f_is_default(palpha_eps)) .or. (.not. f_is_default(ptgalpha_eps))), & - "relax is not set as true, but other phase change parameters have been modified. " // & - "Either activate phase change or set the values to default") - end subroutine s_check_inputs_phase_change - - !> Checks constraints on the Immersed Boundaries parameters. - !! Called by s_check_inputs_common for pre-processing and simulation - impure subroutine s_check_inputs_ibm - @:PROHIBIT(ib .and. n <= 0, "Immersed Boundaries do not work in 1D") - @:PROHIBIT(ib .and. (num_ibs <= 0 .or. num_ibs > num_patches_max), "num_ibs must be between 1 and num_patches_max") - @:PROHIBIT((.not. ib) .and. num_ibs > 0, "num_ibs is set, but ib is not enabled") - end subroutine s_check_inputs_ibm - -#endif - - !> Checks constraints on dimensionality and the number of cells for the grid. - !! Called by s_check_inputs_common for all three stages - impure subroutine s_check_inputs_simulation_domain - @:PROHIBIT(m == dflt_int, "m must be set") - @:PROHIBIT(n == dflt_int, "n must be set") - @:PROHIBIT(p == dflt_int, "p must be set") - @:PROHIBIT(m <= 0) - @:PROHIBIT(n < 0) - @:PROHIBIT(p < 0) - @:PROHIBIT(cyl_coord .and. p > 0 .and. mod(p, 2) /= 1, "p must be odd for cylindrical coordinates") - @:PROHIBIT(n == 0 .and. p > 0, "p must be 0 if n = 0") - end subroutine s_check_inputs_simulation_domain - - !> Checks constraints on model equations and number of fluids in the flow. - !! Called by s_check_inputs_common for all three stages - impure subroutine s_check_inputs_model_eqns_and_num_fluids - @:PROHIBIT(all(model_eqns /= (/1, 2, 3, 4/)), "model_eqns must be 1, 2, 3, or 4") - @:PROHIBIT(num_fluids /= dflt_int .and. num_fluids < 1, "num_fluids must be positive") - @:PROHIBIT(model_eqns == 1 .and. num_fluids /= dflt_int, "num_fluids is not supported for model_eqns = 1") - @:PROHIBIT(model_eqns == 2 .and. num_fluids == dflt_int, "5-equation model (model_eqns = 2) requires num_fluids to be set") - @:PROHIBIT(model_eqns == 3 .and. num_fluids == dflt_int, "6-equation model (model_eqns = 3) requires num_fluids to be set") - @:PROHIBIT(model_eqns == 1 .and. mpp_lim) - @:PROHIBIT(num_fluids == 1 .and. mpp_lim) - @:PROHIBIT(model_eqns == 3 .and. cyl_coord .and. p /= 0, & - "6-equation model (model_eqns = 3) does not support cylindrical coordinates (cyl_coord = T and p != 0)") - end subroutine s_check_inputs_model_eqns_and_num_fluids - - !> Checks constraints regarding IGR order. - !! Called by s_check_inputs_common for all three stages - impure subroutine s_check_inputs_igr - @:PROHIBIT(all(igr_order /= (/3, 5/)), "igr_order must be 3 or 5") - @:PROHIBIT(m + 1 < igr_order, "m must be at least igr_order - 1") - @:PROHIBIT(n > 0 .and. n + 1 < igr_order, "n must be at least igr_order - 1") - @:PROHIBIT(p > 0 .and. p + 1 < igr_order, "p must be at least igr_order - 1") - end subroutine s_check_inputs_igr - - !> Checks constraints regarding WENO order. - !! Called by s_check_inputs_common for all three stages - impure subroutine s_check_inputs_weno - @:PROHIBIT(all(weno_order /= (/1, 3, 5, 7/)), "weno_order must be 1, 3, 5, or 7") - @:PROHIBIT(m + 1 < weno_order, "m must be at least weno_order - 1") - @:PROHIBIT(n > 0 .and. n + 1 < weno_order, "n must be at least weno_order - 1") - @:PROHIBIT(p > 0 .and. p + 1 < weno_order, "p must be at least weno_order - 1") - end subroutine s_check_inputs_weno - - !> Check constraints regarding MUSCL order - !! Called by s_check_inputs_common for all three stages - impure subroutine s_check_inputs_muscl - @:PROHIBIT(all(muscl_order /= (/1, 2/)), "muscl_order must be 1, or 2") - @:PROHIBIT(m + 1 < muscl_order, "m must be at least muscl_order - 1") - @:PROHIBIT(n > 0 .and. n + 1 < muscl_order, "n must be at least muscl_order - 1") - @:PROHIBIT(p > 0 .and. p + 1 < muscl_order, "p must be at least muscl_order - 1") - end subroutine s_check_inputs_muscl - - !> Checks constraints on the boundary conditions in the x-direction. - !! Called by s_check_inputs_common for all three stages - impure subroutine s_check_inputs_bc - logical :: skip_check !< Flag to skip the check when iterating over - !! x, y, and z directions, for special treatment of cylindrical coordinates - - #:for X, VAR in [('x', 'm'), ('y', 'n'), ('z', 'p')] - #:for BOUND in ['beg', 'end'] - @:PROHIBIT(${VAR}$ == 0 .and. bc_${X}$%${BOUND}$ /= dflt_int, "bc_${X}$%${BOUND}$ is not supported for ${VAR}$ = 0") - @:PROHIBIT(${VAR}$ > 0 .and. bc_${X}$%${BOUND}$ == dflt_int, "${VAR}$ != 0 but bc_${X}$%${BOUND}$ is not set") - @:PROHIBIT((bc_${X}$%beg == BC_PERIODIC .and. bc_${X}$%end /= BC_PERIODIC) .or. & - (bc_${X}$%end == BC_PERIODIC .and. bc_${X}$%beg /= BC_PERIODIC), & - "bc_${X}$%beg and bc_${X}$%end must be both periodic (= -1) or both non-periodic") - - ! For cylindrical coordinates, y and z directions use a different check - #:if (X == 'y') or (X == 'z') - skip_check = cyl_coord - #:else - skip_check = .false. - #:endif - - if (.not. skip_check) then - @:PROHIBIT(bc_${X}$%${BOUND}$ /= dflt_int .and. (bc_${X}$%${BOUND}$ > -1 .or. bc_${X}$%${BOUND}$ < BC_DIRICHLET), & - "bc_${X}$%${BOUND}$ must be between -1 and -17") - - @:PROHIBIT(bc_${X}$%${BOUND}$ /= dflt_int .and. bc_${X}$%${BOUND}$ == BC_AXIS, & - "bc_${X}$%${BOUND}$ must not be -14 for non-cylindrical coordinates") - end if - - #:endfor - #:endfor - - @:PROHIBIT(any((/bc_x%beg, bc_x%end, bc_y%beg, bc_y%end, bc_z%beg, bc_z%end/) == BC_NULL), & - "Boundary condition -13 is not supported") - - ! Check for y and z directions for cylindrical coordinates - @: PROHIBIT(cyl_coord .and. n == 0, "n must be positive (2D or 3D) for cylindrical coordinates") - @: PROHIBIT(cyl_coord .and. p == 0 .and. bc_y%beg /= BC_REFLECTIVE, "bc_y%beg must be -2 for 2D cylindrical coordinates (p = 0)") - @: PROHIBIT(cyl_coord .and. p > 0 .and. bc_y%beg /= BC_AXIS, "bc_y%beg must be -14 for 3D cylindrical coordinates (p > 0)") - @: PROHIBIT(cyl_coord .and. (bc_y%end > BC_PERIODIC .or. bc_y%end < BC_DIRICHLET), "bc_y%end must be between -1 and -17") - @: PROHIBIT(cyl_coord .and. bc_y%end == BC_AXIS, "bc_y%end must not be -14") - - ! Check for y and z directions for 3D cylindrical coordinates - @: PROHIBIT(cyl_coord .and. p > 0 .and. (bc_z%beg /= BC_PERIODIC .and. bc_z%beg /= BC_REFLECTIVE), & - "bc_z%beg must be -1 or -2 for 3D cylindrical coordinates") - - @: PROHIBIT(cyl_coord .and. p > 0 .and. (bc_z%end /= BC_PERIODIC .and. bc_z%end /= BC_REFLECTIVE), & - "bc_z%end must be -1 or -2 for 3D cylindrical coordinates") - -#ifndef MFC_POST_PROCESS - if (num_bc_patches > 0) then - #:for DIR in [('x'), ('y'), ('z')] - #:for LOC in [('beg'), ('end')] - @:PROHIBIT(bc_${DIR}$%${LOC}$ == -1 .or. (bc_${DIR}$%${LOC}$ <= -4 .and. bc_${DIR}$%${LOC}$ >= -14), & - "bc_${DIR}$%${LOC}$ is not compatible with num_bc_patches > 0") - #:endfor - #:endfor - end if -#endif - - end subroutine s_check_inputs_bc - - !> Checks constraints on the stiffened equation of state fluids parameters. - !! Called by s_check_inputs_common for all three stages - impure subroutine s_check_inputs_stiffened_eos - character(len=5) :: iStr !< for int to string conversion - integer :: bub_fac !< For allowing an extra fluid_pp if there are subgrid bubbles_euler - integer :: i - - bub_fac = 0 - if (bubbles_euler .and. (num_fluids == 1)) bub_fac = 1 - - do i = 1, num_fluids - call s_int_to_str(i, iStr) - @:PROHIBIT(.not. f_is_default(fluid_pp(i)%gamma) .and. fluid_pp(i)%gamma <= 0._wp, & - "fluid_pp("//trim(iStr)//")%gamma must be positive") - - @:PROHIBIT(model_eqns == 1 .and. (.not. f_is_default(fluid_pp(i)%gamma)), & - "model_eqns = 1 does not support fluid_pp("//trim(iStr)//")%gamma") - - @:PROHIBIT((i <= num_fluids + bub_fac .and. fluid_pp(i)%gamma <= 0._wp) .or. & - (i > num_fluids + bub_fac .and. (.not. f_is_default(fluid_pp(i)%gamma))), & - "for fluid_pp("//trim(iStr)//")%gamma") - - @:PROHIBIT(.not. f_is_default(fluid_pp(i)%pi_inf) .and. fluid_pp(i)%pi_inf < 0._wp, & - "fluid_pp("//trim(iStr)//")%pi_inf must be non-negative") - - @:PROHIBIT(model_eqns == 1 .and. (.not. f_is_default(fluid_pp(i)%pi_inf)), & - "model_eqns = 1 does not support fluid_pp("//trim(iStr)//")%pi_inf") - - @:PROHIBIT((i <= num_fluids + bub_fac .and. fluid_pp(i)%pi_inf < 0._wp) .or. & - (i > num_fluids + bub_fac .and. (.not. f_is_default(fluid_pp(i)%pi_inf))), & - "for fluid_pp("//trim(iStr)//")%pi_inf") - - @:PROHIBIT(fluid_pp(i)%cv < 0._wp, & - "fluid_pp("//trim(iStr)//")%cv must be positive") - end do - end subroutine s_check_inputs_stiffened_eos - - !> Checks constraints on the surface tension parameters. - !! Called by s_check_inputs_common for all three stages - impure subroutine s_check_inputs_surface_tension - - integer :: i - - @:PROHIBIT(surface_tension .and. sigma < 0._wp, & - "sigma must be greater than or equal to zero") - - @:PROHIBIT(surface_tension .and. f_approx_equal(sigma, dflt_real), & - "sigma must be set if surface_tension is enabled") - - @:PROHIBIT(.not. f_is_default(sigma) .and. .not. surface_tension, & - "sigma is set but surface_tension is not enabled") - - @:PROHIBIT(surface_tension .and. (model_eqns /= 3 .and. model_eqns /=2), & - "The surface tension model requires model_eqns=3 or model_eqns=2") - - @:PROHIBIT(surface_tension .and. num_fluids /= 2, & - "The surface tension model requires num_fluids=2") - -#ifdef MFC_PRE_PROCESS - do i = 1, num_patches - @:PROHIBIT(surface_tension .and. f_is_default(patch_icpp(i)%cf_val), & - "patch_icpp(i)%cf_val must be set if surface_tension is enabled") - end do -#endif MFC_PRE_PROCESS - - end subroutine s_check_inputs_surface_tension - - !> Checks constraints on the inputs for moving boundaries. - !! Called by s_check_inputs_common for all three stages - impure subroutine s_check_inputs_moving_bc - #:for X, VB2, VB3 in [('x', 'vb2', 'vb3'), ('y', 'vb3', 'vb1'), ('z', 'vb1', 'vb2')] - if (.not. (f_approx_equal(bc_${X}$%vb1, 0._wp) .and. & - f_approx_equal(bc_${X}$%vb2, 0._wp) .and. & - f_approx_equal(bc_${X}$%vb3, 0._wp))) then - if (bc_${X}$%beg == BC_SLIP_WALL) then - if (.not. (f_approx_equal(bc_${X}$%${VB2}$, 0._wp) .and. & - f_approx_equal(bc_${X}$%${VB3}$, 0._wp))) then - call s_mpi_abort("bc_${X}$%beg must be -15 if "// & - "bc_${X}$%${VB2}$ or bc_${X}$%${VB3}$ "// & - "is set. Exiting.", CASE_FILE_ERROR_CODE) - end if - elseif (bc_${X}$%beg /= BC_NO_SLIP_WALL) then - call s_mpi_abort("bc_${X}$%beg must be -15 or -16 if "// & - "bc_${X}$%vb[1,2,3] is set. Exiting.", CASE_FILE_ERROR_CODE) - end if - end if - #:endfor - - #:for X, VE2, VE3 in [('x', 've2', 've3'), ('y', 've3', 've1'), ('z', 've1', 've2')] - if (.not. (f_approx_equal(bc_${X}$%ve1, 0._wp) .and. & - f_approx_equal(bc_${X}$%ve2, 0._wp) .and. & - f_approx_equal(bc_${X}$%ve3, 0._wp))) then - if (bc_${X}$%end == BC_SLIP_WALL) then - if (.not. (f_approx_equal(bc_${X}$%${VE2}$, 0._wp) .and. & - f_approx_equal(bc_${X}$%${VE3}$, 0._wp))) then - call s_mpi_abort("bc_${X}$%end must be -15 if "// & - "bc_${X}$%${VE2}$ or bc_${X}$%${VE3}$ "// & - "is set. Exiting.", CASE_FILE_ERROR_CODE) - end if - elseif (bc_${X}$%end /= BC_NO_SLIP_WALL) then - call s_mpi_abort("bc_${X}$%end must be -15 or -16 if "// & - "bc_${X}$%ve[1,2,3] is set. Exiting.", CASE_FILE_ERROR_CODE) - end if - end if - #:endfor - end subroutine s_check_inputs_moving_bc - - impure subroutine s_check_inputs_mhd - @:PROHIBIT(mhd .and. num_fluids /= 1, "MHD is only available for single-component flows") - @:PROHIBIT(mhd .and. model_eqns /= 2, "MHD is only available for the 5-equation model") - - @:PROHIBIT(relativity .and. .not. mhd) - - @:PROHIBIT(.not. mhd .and. (.not. f_is_default(Bx0)), "Bx0 must not be set if MHD is not enabled") - @:PROHIBIT(mhd .and. n == 0 .and. f_is_default(Bx0), "Bx0 must be set in 1D MHD simulations") - @:PROHIBIT(mhd .and. n > 0 .and. (.not. f_is_default(Bx0)), "Bx0 must not be set in 2D/3D MHD simulations") - end subroutine s_check_inputs_mhd end module m_checker_common diff --git a/src/post_process/m_checker.fpp b/src/post_process/m_checker.fpp index 07ff3f7bd..291b9f597 100644 --- a/src/post_process/m_checker.fpp +++ b/src/post_process/m_checker.fpp @@ -26,150 +26,24 @@ contains impure subroutine s_check_inputs call s_check_inputs_output_format - call s_check_inputs_partial_domain - call s_check_inputs_partial_density - call s_check_inputs_velocity - call s_check_inputs_flux_limiter - call s_check_inputs_volume_fraction - call s_check_inputs_vorticity - call s_check_inputs_qm - call s_check_inputs_liutex - call s_check_inputs_schlieren - call s_check_inputs_surface_tension - call s_check_inputs_no_flow_variables end subroutine s_check_inputs !> Checks constraints on output format parameters impure subroutine s_check_inputs_output_format - @:PROHIBIT(format /= 1 .and. format /= 2) - @:PROHIBIT(precision /= 1 .and. precision /= 2) @:PROHIBIT(precision == 2 .and. wp == sp) end subroutine s_check_inputs_output_format - !> Checks constraints on partial domain parameters - impure subroutine s_check_inputs_partial_domain - @:PROHIBIT(output_partial_domain .and. format == 1) - @:PROHIBIT(output_partial_domain .and. precision == 1) - @:PROHIBIT(output_partial_domain .and. any([flux_wrt, heat_ratio_wrt, pres_inf_wrt, c_wrt, schlieren_wrt, qm_wrt, liutex_wrt, ib, any(omega_wrt)])) - @:PROHIBIT(output_partial_domain .and. (f_is_default(x_output%beg) .or. f_is_default(x_output%end))) - @:PROHIBIT(output_partial_domain .and. n /= 0 .and. (f_is_default(y_output%beg) .or. f_is_default(y_output%end))) - @:PROHIBIT(output_partial_domain .and. p /= 0 .and. (f_is_default(z_output%beg) .or. f_is_default(z_output%end))) - - #:for X in ['x', 'y', 'z'] - @:PROHIBIT(${X}$_output%beg > ${X}$_output%end) - #:endfor - end subroutine s_check_inputs_partial_domain - - !> Checks constraints on partial density parameters - impure subroutine s_check_inputs_partial_density - character(len=5) :: iStr - integer :: i - - do i = 1, num_fluids - call s_int_to_str(i, iStr) - @:PROHIBIT(alpha_rho_wrt(i) .and. model_eqns == 1, "alpha_rho_wrt("//trim(iStr)//") is not supported for model_eqns = 1") - @:PROHIBIT(alpha_rho_wrt(i) .and. i > num_fluids, "Index of alpha_rho_wrt("//trim(iStr)//") exceeds the total number of fluids") - end do - end subroutine s_check_inputs_partial_density - - !> Checks constraints on momentum parameters - impure subroutine s_check_inputs_momentum - @:PROHIBIT(n == 0 .and. mom_wrt(2)) - @:PROHIBIT(p == 0 .and. mom_wrt(3)) - end subroutine s_check_inputs_momentum - - !> Checks constraints on velocity parameters - impure subroutine s_check_inputs_velocity - @:PROHIBIT(n == 0 .and. vel_wrt(2)) - @:PROHIBIT(p == 0 .and. vel_wrt(3)) - end subroutine s_check_inputs_velocity - - !> Checks constraints on flux limiter parameters - impure subroutine s_check_inputs_flux_limiter - @:PROHIBIT(n == 0 .and. flux_wrt(2)) - @:PROHIBIT(p == 0 .and. flux_wrt(3)) - @:PROHIBIT(all(flux_lim /= (/dflt_int, 1, 2, 3, 4, 5, 6, 7/)), "flux_lim must be between 1 and 7") - end subroutine s_check_inputs_flux_limiter - - !> Checks constraints on volume fraction parameters - impure subroutine s_check_inputs_volume_fraction - character(len=5) :: iStr - integer :: i - - do i = 1, num_fluids - call s_int_to_str(i, iStr) - @:PROHIBIT(alpha_wrt(i) .and. model_eqns == 1, "alpha_wrt("//trim(iStr)//") is not supported for model_eqns = 1") - @:PROHIBIT(alpha_wrt(i) .and. i > num_fluids, "Index of alpha_wrt("//trim(iStr)//") exceeds the total number of fluids") - end do - end subroutine s_check_inputs_volume_fraction - - !> Checks constraints on vorticity parameters - impure subroutine s_check_inputs_vorticity - @:PROHIBIT(n == 0 .and. any(omega_wrt)) - @:PROHIBIT(p == 0 .and. (omega_wrt(1) .or. omega_wrt(2))) - @:PROHIBIT(any(omega_wrt) .and. fd_order == dflt_int, "fd_order must be set for omega_wrt") - end subroutine s_check_inputs_vorticity - !> Checks constraints on fft_wrt impure subroutine s_check_inputs_fft integer :: num_procs_y, num_procs_z - @:PROHIBIT(fft_wrt .and. (n == 0 .or. p == 0), "FFT WRT only in 3D") - @:PROHIBIT(fft_wrt .and. cyl_coord, "FFT WRT incompatible with cylindrical coordinates") - @:PROHIBIT(fft_wrt .and. (MOD(m_glb+1,2) == 1 .or. MOD(n_glb+1,2) == 1 .or. MOD(p_glb+1,2) == 1), "FFT WRT requires global dimensions divisible by 2") @:PROHIBIT(fft_wrt .and. MOD(n_glb+1,n+1) /= 0, "FFT WRT requires n_glb to be divisible by num_procs_y") @:PROHIBIT(fft_wrt .and. MOD(p_glb+1,p+1) /= 0, "FFT WRT requires p_glb to be divisible by num_procs_z") num_procs_y = (n_glb + 1)/(n + 1) num_procs_z = (p_glb + 1)/(p + 1) @:PROHIBIT(fft_wrt .and. MOD(m_glb+1,num_procs_y) /= 0, "FFT WRT requires m_glb to be divisible by num_procs_y") @:PROHIBIT(fft_wrt .and. MOD(n_glb+1,num_procs_z) /= 0, "FFT WRT requires n_glb to be divisible by num_procs_z") - @:PROHIBIT(fft_wrt .and. (bc_x%beg < -1 .or. bc_y%beg < -1 .or. bc_z%beg < -1 .or. bc_x%end < -1 .or. bc_y%end < -1 .or. bc_z%end < -1), "FFT WRT requires periodic BCs") end subroutine s_check_inputs_fft - !> Checks constraints on Q-criterion parameters - impure subroutine s_check_inputs_qm - @:PROHIBIT(n == 0 .and. qm_wrt) - end subroutine s_check_inputs_qm - - !> Checks constraints on liutex parameters - impure subroutine s_check_inputs_liutex - @:PROHIBIT(n == 0 .and. liutex_wrt) - end subroutine s_check_inputs_liutex - - !> Checks constraints on numerical Schlieren parameters - !! (schlieren_wrt and schlieren_alpha) - impure subroutine s_check_inputs_schlieren - character(len=5) :: iStr - integer :: i - - @:PROHIBIT(n == 0 .and. schlieren_wrt) - @:PROHIBIT(schlieren_wrt .and. fd_order == dflt_int, "fd_order must be set for schlieren_wrt") - - do i = 1, num_fluids - call s_int_to_str(i, iStr) - @:PROHIBIT(.not. f_is_default(schlieren_alpha(i)) .and. schlieren_alpha(i) <= 0._wp, & - "schlieren_alpha("//trim(iStr)//") must be greater than zero") - @:PROHIBIT(.not. f_is_default(schlieren_alpha(i)) .and. i > num_fluids, & - "Index of schlieren_alpha("//trim(iStr)//") exceeds the total number of fluids") - @:PROHIBIT(.not. f_is_default(schlieren_alpha(i)) .and. (.not. schlieren_wrt), & - "schlieren_alpha("//trim(iStr)//") should be set only with schlieren_wrt enabled") - end do - end subroutine s_check_inputs_schlieren - - !> Checks constraints on surface tension parameters (cf_wrt and sigma) - impure subroutine s_check_inputs_surface_tension - @:PROHIBIT(cf_wrt .and. .not. surface_tension, & - "cf_wrt can only be enabled if the surface coefficient is set") - end subroutine s_check_inputs_surface_tension - - !> Checks constraints on the absence of flow variables - impure subroutine s_check_inputs_no_flow_variables - @:PROHIBIT(.not. any([ & - (/rho_wrt, E_wrt, pres_wrt, gamma_wrt, heat_ratio_wrt, pi_inf_wrt, & - pres_inf_wrt, cons_vars_wrt, prim_vars_wrt, c_wrt, schlieren_wrt/), & - alpha_rho_wrt, mom_wrt, vel_wrt, flux_wrt, alpha_wrt, omega_wrt]), & - "None of the flow variables have been selected for post-process. Exiting.") - end subroutine s_check_inputs_no_flow_variables - end module m_checker diff --git a/src/pre_process/m_checker.fpp b/src/pre_process/m_checker.fpp index def7aebce..5499262c6 100644 --- a/src/pre_process/m_checker.fpp +++ b/src/pre_process/m_checker.fpp @@ -26,14 +26,6 @@ contains impure subroutine s_check_inputs call s_check_parallel_io - call s_check_inputs_restart - call s_check_inputs_grid_stretching - call s_check_inputs_qbmm_and_polydisperse - call s_check_inputs_perturb_density - call s_check_inputs_chemistry - call s_check_inputs_misc - call s_check_bc - call s_check_simplex_noise end subroutine s_check_inputs @@ -42,245 +34,6 @@ contains #ifndef MFC_MPI @:PROHIBIT(parallel_io, "MFC built with --no-mpi requires parallel_io=F") #endif - @:PROHIBIT(down_sample .and. (.not. parallel_io), "down sample with parallel_io = T") - @:PROHIBIT(down_sample .and. (.not. igr), "down sample with igr = T") - @:PROHIBIT(down_sample .and. (p == 0), "down sample with 3D only") - @:PROHIBIT(down_sample .and. (.not. file_per_process), "down sample with file_per_process = T") - @:PROHIBIT(down_sample .and. (MOD(m+1,3) > 0), "down sample with m divisible by 3") - @:PROHIBIT(down_sample .and. (MOD(n+1,3) > 0), "down sample with n divisible by 3") - @:PROHIBIT(down_sample .and. (MOD(p+1,3) > 0), "down sample with p divisible by 3") - end subroutine s_check_parallel_io - !> Checks constraints on the restart parameters - !! (old_grid, old_ic, etc.) - impure subroutine s_check_inputs_restart - logical :: skip_check !< Flag to skip the check when iterating over - !! x, y, and z directions, for special treatment of cylindrical coordinates - integer :: i - - @:PROHIBIT((.not. old_grid) .and. old_ic, & - "old_ic can only be enabled with old_grid enabled") - - @:PROHIBIT(old_grid .and. t_step_old == dflt_int, & - "old_grid requires t_step_old to be set") - @:PROHIBIT(old_grid .and. ((.not. f_is_default(x_domain%beg)) .or. (.not. f_is_default(x_domain%end))), & - "x_domain is not supported with old_grid enabled") - @:PROHIBIT(old_grid .and. ((.not. f_is_default(y_domain%beg)) .or. (.not. f_is_default(y_domain%end))), & - "y_domain is not supported with old_grid enabled") - @:PROHIBIT(old_grid .and. ((.not. f_is_default(z_domain%beg)) .or. (.not. f_is_default(z_domain%end))), & - "z_domain is not supported with old_grid enabled") - - #:for DIR, VAR in [('x', 'm'), ('y', 'n'), ('z', 'p')] - ! For cylindrical coordinates, the y and z directions use a different check - #:if (DIR == 'y') or (DIR == 'z') - skip_check = cyl_coord - #:else - skip_check = .false. - #:endif - - if (.not. skip_check) then - #:for BOUND in ['beg', 'end'] - @:PROHIBIT(${VAR}$ == 0 .and. (.not. f_is_default((${DIR}$_domain%${BOUND}$))), & - "${DIR}$_domain%${BOUND}$ must not be set when ${VAR}$ = 0") - @:PROHIBIT(${VAR}$ > 0 .and. old_grid .and. (.not. f_is_default(${DIR}$_domain%${BOUND}$)), & - "${DIR}$_domain%${BOUND}$ must not be set when ${VAR}$ > 0 and old_grid = T") - @:PROHIBIT(${VAR}$ > 0 .and. (.not. old_grid) .and. f_is_default(${DIR}$_domain%${BOUND}$), & - "${DIR}$_domain%${BOUND}$ must be set when ${VAR}$ > 0 and old_grid = F") - @:PROHIBIT(${VAR}$ > 0 .and. (.not. old_grid) .and. ${DIR}$_domain%beg >= ${DIR}$_domain%end, & - "${DIR}$_domain%beg must be less than ${DIR}$_domain%end when both are set") - #:endfor - end if - - #:endfor - - @:PROHIBIT(cyl_coord .and. n == 0, & - "n must be positive (2D or 3D) for cylindrical coordinates") - @:PROHIBIT(cyl_coord .and. (f_is_default(y_domain%beg) .or. f_is_default(y_domain%end)), & - "y_domain%beg and y_domain%end must be set for n = 0 (2D cylindrical coordinates)") - @:PROHIBIT(cyl_coord .and. ((.not. f_approx_equal(y_domain%beg , 0._wp)) .or. y_domain%end <= 0._wp), & - "y_domain%beg must be 0 and y_domain%end must be positive for cylindrical coordinates") - @:PROHIBIT(cyl_coord .and. p == 0 .and. ((.not. f_is_default(z_domain%beg)) .or. (.not. f_is_default(z_domain%end))), & - "z_domain%beg and z_domain%end are not supported for p = 0 (2D cylindrical coordinates)") - @:PROHIBIT(cyl_coord .and. p > 0 .and. (.not. (f_approx_equal(z_domain%beg, 0._wp) .and. f_approx_equal(z_domain%end, 2._wp*pi))), & - "z_domain%beg must be 0 and z_domain%end must be 2*pi for 3D cylindrical coordinates") - - @:PROHIBIT(num_patches < 0) - @:PROHIBIT(num_patches == 0 .and. t_step_old == dflt_int, & - "num_patches must be positive for the non-restart case") - - end subroutine s_check_inputs_restart - - !> Checks constraints on grid stretching parameters - !! (loops_x[y,z], stretch_x[y,z], etc.) - impure subroutine s_check_inputs_grid_stretching - ! Constraints on loops for grid stretching - @:PROHIBIT(loops_x < 1) - @:PROHIBIT(loops_y < 1) - - ! Constraints specific to stretch_y - @:PROHIBIT(stretch_y .and. n == 0) - - ! Constraints specific to stretch_z - @:PROHIBIT(stretch_z .and. p == 0) - @:PROHIBIT(stretch_z .and. cyl_coord) - - ! Common checks for all directions (stretch_x, stretch_y, and stretch_z) - #:for X in ['x', 'y', 'z'] - @:PROHIBIT(stretch_${X}$ .and. old_grid, "old_grid and stretch_${X}$ are incompatible") - @:PROHIBIT(stretch_${X}$ .and. f_is_default(a_${X}$), "a_${X}$ must be set with stretch_${X}$ enabled") - @:PROHIBIT(stretch_${X}$ .and. f_is_default(${X}$_a), "${X}$_a must be set with stretch_${X}$ enabled") - @:PROHIBIT(stretch_${X}$ .and. f_is_default(${X}$_b), "${X}$_b must be set with stretch_${X}$ enabled") - @:PROHIBIT(stretch_${X}$ .and. ${X}$_a >= ${X}$_b, "${X}$_a must be less than ${X}$_b with stretch_${X}$ enabled") - !&< Deactivate prettify - @:PROHIBIT(stretch_${X}$ .and. (a_${X}$ + log(cosh(a_${X}$*(${X}$_domain%beg - ${X}$_a))) & - + log(cosh(a_${X}$*(${X}$_domain%beg - ${X}$_b))) & - - 2._wp*log(cosh(0.5_wp*a_${X}$*(${X}$_b - ${X}$_a)))) / a_${X}$ <= 0._wp, & - "${X}$_domain%beg is too close to ${X}$_a and ${X}$_b for the given a_${X}$") - @:PROHIBIT(stretch_${X}$ .and. (a_${X}$ + log(cosh(a_${X}$*(${X}$_domain%end - ${X}$_a))) & - + log(cosh(a_${X}$*(${X}$_domain%end - ${X}$_b))) & - - 2._wp*log(cosh(0.5_wp*a_${X}$*(${X}$_b - ${X}$_a)))) / a_${X}$ <= 0._wp, & - "${X}$_domain%end is too close to ${X}$_a and ${X}$_b for the given a_${X}$") - !&> - #:endfor - end subroutine s_check_inputs_grid_stretching - - !> Checks constraints on the QBMM and polydisperse bubble parameters - !! (qbmm, polydisperse, dist_type and rhoRV) - impure subroutine s_check_inputs_qbmm_and_polydisperse - @:PROHIBIT(qbmm .and. dist_type == dflt_int, "dist_type must be set if using QBMM") - @:PROHIBIT(qbmm .and. dist_type /= 1 .and. rhoRV > 0._wp, "rhoRV cannot be used with dist_type != 1") - end subroutine s_check_inputs_qbmm_and_polydisperse - - !> Checks constraints on initial partial density perturbation - !! (perturb_flow, perturb_flow_fluid, perturb_flow_mag, perturb_sph, - !! perturb_sph_fluid, and fluid_rho) - impure subroutine s_check_inputs_perturb_density - character(len=5) :: iStr !< for int to string conversion - integer :: i - - @:PROHIBIT(perturb_flow .and. (perturb_flow_fluid == dflt_int .or. f_is_default(perturb_flow_mag)), & - "perturb_flow_fluid and perturb_flow_mag must be set with perturb_flow = T") - @:PROHIBIT((.not. perturb_flow) .and. (perturb_flow_fluid /= dflt_int .or. (.not. f_is_default(perturb_flow_mag))), & - "perturb_flow_fluid and perturb_flow_mag must not be set with perturb_flow = F") - @:PROHIBIT(perturb_flow_fluid > num_fluids .or. (perturb_flow_fluid < 0 .and. perturb_flow_fluid /= dflt_int), & - "perturb_flow_fluid must be between 0 and num_fluids") - @:PROHIBIT(perturb_sph .and. perturb_sph_fluid == dflt_int, & - "perturb_sph_fluid must be set with perturb_sph = T") - @:PROHIBIT((.not. perturb_sph) .and. perturb_sph_fluid /= dflt_int, & - "perturb_sph_fluid must not be set with perturb_sph = F") - @:PROHIBIT(perturb_sph_fluid > num_fluids .or. (perturb_sph_fluid < 0 .and. perturb_sph_fluid /= dflt_int), & - "perturb_sph_fluid must be between 0 and num_fluids") - @:PROHIBIT((.not. perturb_sph) .and. (.not. f_all_default(fluid_rho)), & - "fluid_rho must not be set with perturb_sph = F") - - do i = 1, num_fluids - call s_int_to_str(i, iStr) - @:PROHIBIT(perturb_sph .and. f_is_default(fluid_rho(i)), & - "fluid_rho("//trim(iStr)//") must be set if perturb_sph = T") - end do - end subroutine s_check_inputs_perturb_density - - impure subroutine s_check_inputs_chemistry - - if (chemistry) then - @:ASSERT(num_species > 0) - end if - - end subroutine s_check_inputs_chemistry - - !> Checks miscellaneous constraints - !! (mixlayer_vel_profile and mixlayer_perturb) - impure subroutine s_check_inputs_misc - ! Hypertangent velocity profile - @:PROHIBIT(mixlayer_vel_profile .and. (n == 0), & - "mixlayer_vel_profile requires n > 0") - - ! Instability wave - @:PROHIBIT(mixlayer_perturb .and. p == 0, "mixlayer_perturb requires p > 0") - @:PROHIBIT(elliptic_smoothing .and. elliptic_smoothing_iters < 1, & - "elliptic_smoothing_iters must be positive") - - end subroutine s_check_inputs_misc - - impure subroutine s_check_bc - - integer :: i - character(len=5) :: iStr !< for int to string conversion - - call s_int_to_str(num_bc_patches_max, iStr) - @:PROHIBIT(num_bc_patches > num_bc_patches_max, "num_bc_patches must be <= "//trim(iStr)) - - do i = 1, num_bc_patches - call s_int_to_str(i, iStr) - - ! Line Segment BC - if (patch_bc(i)%geometry == 1) then - @:PROHIBIT( .not. f_is_default(patch_bc(i)%radius), "Line Segment Patch can't have radius defined") - #:for DIR in [('1'), ('2')] - @:PROHIBIT(patch_bc(i)%dir == ${DIR}$ .and. (.not. f_is_default(patch_bc(i)%centroid(${DIR}$)) .or. .not. f_is_default(patch_bc(i)%centroid(3))), & - "Line Segment Patch of Dir ${DIR}$ can't have a centroid in Dir ${DIR}$ or 3" ) - @:PROHIBIT(patch_bc(i)%dir == ${DIR}$ .and. (.not. f_is_default(patch_bc(i)%length(${DIR}$)) .or. .not. f_is_default(patch_bc(i)%length(3))), & - "Line Segment Patch of Dir ${DIR}$ can't have a length in Dir ${DIR}$ or 3" ) - #:endfor - end if - - ! Circle BC - if (patch_bc(i)%geometry == 2) then - @:PROHIBIT(f_is_default(patch_bc(i)%radius), "Circle Patch must have radius defined") - @:PROHIBIT(.not. f_is_default(patch_bc(i)%length(1)) .or. .not. f_is_default(patch_bc(i)%length(2)) .or. .not. f_is_default(patch_bc(i)%length(3)), & - "Circle Patch can't have lengths defined") - - #:for DIR in [('1'), ('2'), ('3')] - @:PROHIBIT(patch_bc(i)%dir == ${DIR}$ .and. .not. f_is_default(patch_bc(i)%centroid(${DIR}$)), & - "Circle Patch of Dir ${DIR}$ can't have a centroid in Dir ${DIR}$") - #:endfor - end if - - ! Rectangle BC - if (patch_bc(i)%geometry == 3) then - @:PROHIBIT( .not. f_is_default(patch_bc(i)%radius), "Rectangle Patch can't have radius defined") - - #:for DIR, VAR in [('1', '2', '3'), ('2', '3', '1'), ('3', '1', '2')] - @:PROHIBIT(patch_bc(i)%dir == ${DIR}$ .and. .not. f_is_default(patch_bc(i)%centroid(${DIR}$)), & - "Rectangle Patch of Dir ${DIR}$ can't have a centroid in Dir ${DIR}$") - @:PROHIBIT(patch_bc(i)%dir == ${DIR}$ .and. .not. f_is_default(patch_bc(i)%length(${DIR}$)), & - "Rectangle Patch of Dir ${DIR}$ can't have a length in Dir ${DIR}$") - #:endfor - end if - - ! Incompatible BC check - @:PROHIBIT(((patch_bc(i)%type >= BC_AXIS .and. patch_bc(i)%type <= BC_RIEMANN_EXTRAP) .or. & - (patch_bc(i)%type == BC_PERIODIC) .or. patch_bc(i)%type < BC_DIRICHLET), & - "Incompatible BC type for boundary condition patch "//trim(iStr)) - end do - - end subroutine s_check_bc - - impure subroutine s_check_moving_IBM - - end subroutine s_check_moving_IBM - - impure subroutine s_check_simplex_noise - - if (simplex_perturb) then - #:for DIR in [1, 2, 3] - if (simplex_params%perturb_vel(${DIR}$)) then - @:PROHIBIT(simplex_params%perturb_vel_freq(${DIR}$) == dflt_real, & - "simplex_params%perturb_vel_freq(${DIR}$) must be set if" // & - "simplex_params%perturb_vel(${DIR}$) is true") - @:PROHIBIT(simplex_params%perturb_vel_scale(${DIR}$) == dflt_real, & - "simplex_params%perturb_vel_scale(${DIR}$) must be set if" // & - "simplex_params%perturb_vel(${DIR}$) is true") - #:for DIM in [1, 2, 3] - @:PROHIBIT(simplex_params%perturb_vel_offset(${DIR}$,${DIM}$) == dflt_real, & - "simplex_params%perturb_vel_scale(${DIR}$,${DIM}$) must be set if" // & - "simplex_params%perturb_vel(${DIR}$) is true") - #:endfor - end if - #:endfor - end if - - end subroutine - end module m_checker diff --git a/src/simulation/m_checker.fpp b/src/simulation/m_checker.fpp index da82e0f37..9ec3c6981 100644 --- a/src/simulation/m_checker.fpp +++ b/src/simulation/m_checker.fpp @@ -29,7 +29,6 @@ contains call s_check_inputs_compilers if (igr) then - call s_check_inputs_igr call s_check_inputs_nvidia_uvm else if (recon_type == WENO_TYPE) then @@ -37,24 +36,10 @@ contains else if (recon_type == MUSCL_TYPE) then call s_check_inputs_muscl end if - call s_check_inputs_riemann_solver - call s_check_inputs_model_eqns - call s_check_inputs_acoustic_src - call s_check_inputs_hypoelasticity - call s_check_inputs_bubbles_euler - call s_check_inputs_bubbles_lagrange - call s_check_inputs_adapt_dt - call s_check_inputs_alt_soundspeed - call s_check_inputs_grcbc call s_check_inputs_geometry_precision - call s_check_inputs_mhd - call s_check_inputs_continuum_damage end if call s_check_inputs_time_stepping - call s_check_inputs_stiffened_eos_viscosity - call s_check_inputs_body_forces - call s_check_inputs_misc end subroutine s_check_inputs @@ -65,35 +50,6 @@ contains #endif end subroutine s_check_inputs_compilers - impure subroutine s_check_inputs_igr - @:PROHIBIT(num_igr_iters < 0, "num_igr_iters must be greater than 0") - @:PROHIBIT(num_igr_warm_start_iters < 0, "num_igr_warm_start_iters must be greater than 0") - @:PROHIBIT((igr_iter_solver /= 1 .and. igr_iter_solver /= 2), & - "igr_iter_solver must be 1 or 2") - @:PROHIBIT(alf_factor < 0, "alf factor must be positive") - @:PROHIBIT(model_eqns /= 2, "IGR only supports model_eqns = 2") - @:PROHIBIT(ib, "IGR does not support the immersed boundary method") - @:PROHIBIT(bubbles_euler, "IGR does not support Euler-Euler bubble models") - @:PROHIBIT(bubbles_lagrange, "IGR does not support Euler-Lagrange bubbles models") - @:PROHIBIT(alt_soundspeed, "IGR does not support alt_soundspeed = T") - @:PROHIBIT(surface_tension, "IGR does not support surface tension") - @:PROHIBIT(hypoelasticity, "IGR does not support hypoelasticity") - @:PROHIBIT(acoustic_source, "IGR does not support acoustic sources") - @:PROHIBIT(relax, "IGR does not support phase change") - @:PROHIBIT(mhd, "IGR does not support magnetohydrodynamics") - @:PROHIBIT(hyperelasticity, "IGR does not support hyperelasticity") - @:PROHIBIT(cyl_coord, "IGR does not support cylindrical or axisymmetric coordinates") - @:PROHIBIT(probe_wrt, "IGR does not support probe writes") - - #:for DIR in [('x'), ('y'), ('z')] - #:for LOC in [('beg'), ('end')] - @:PROHIBIT((bc_${DIR}$%${LOC}$ <= -4 .and. bc_${DIR}$%${LOC}$ >= -14), & - "Characteristic boundary condition bc_${DIR}$%${LOC}$ is not compatible with IGR") - #:endfor - #:endfor - - end subroutine s_check_inputs_igr - !> Checks constraints on WENO scheme parameters impure subroutine s_check_inputs_weno character(len=5) :: numStr !< for int to string conversion @@ -105,23 +61,6 @@ contains "For 2D simulation, n must be greater than or equal to (num_stcls_min*weno_order - 1), whose value is "//trim(numStr)) @:PROHIBIT(p + 1 < min(1, p)*num_stcls_min*weno_order, & "For 3D simulation, p must be greater than or equal to (num_stcls_min*weno_order - 1), whose value is "//trim(numStr)) - @:PROHIBIT(weno_order /= 1 .and. f_is_default(weno_eps), & - "weno_order != 1, but weno_eps is not set. A typical value of weno_eps is 1e-6") - - @:PROHIBIT(weno_eps <= 0._wp, "weno_eps must be positive. A typical value of weno_eps is 1e-6") - @:PROHIBIT(wenoz .and. weno_order == 7 .and. f_is_default(real(wenoz_q, wp)), & - "wenoz is used at 7th order, but wenoz_q is not set. It should be either 2, 3, or 4") - @:PROHIBIT(wenoz .and. weno_order == 7 .and. .not. (f_approx_equal(real(wenoz_q, wp), real(2, wp)) .or. & - f_approx_equal(real(wenoz_q, wp), real(3, wp)) .or. f_approx_equal(real(wenoz_q, wp), real(4, wp))), & - "wenoz_q must be either 2, 3, or 4") - @:PROHIBIT(teno .and. f_is_default(teno_CT), "teno is used, but teno_CT is not set. A typical value of teno_CT is 1e-6") - @:PROHIBIT(teno .and. teno_CT <= 0._wp, "teno_CT must be positive. A typical value of teno_CT is 1e-6") - @:PROHIBIT(count([mapped_weno, wenoz, teno]) >= 2, "Only one of mapped_weno, wenoz, or teno can be set to true") - @:PROHIBIT(weno_order == 1 .and. mapped_weno) - @:PROHIBIT(weno_order == 1 .and. wenoz) - @:PROHIBIT((weno_order == 1 .or. weno_order == 3) .and. teno) - @:PROHIBIT(weno_order /= 5 .and. mp_weno) - @:PROHIBIT(model_eqns == 1 .and. weno_avg) end subroutine s_check_inputs_weno impure subroutine s_check_inputs_muscl @@ -134,26 +73,8 @@ contains "For 2D simulation, n must be greater than or equal to (num_stcls_min*muscl_order - 1), whose value is "//trim(numStr)) @:PROHIBIT(p + 1 < min(1, p)*num_stcls_min*muscl_order, & "For 3D simulation, p must be greater than or equal to (num_stcls_min*muscl_order - 1), whose value is "//trim(numStr)) - @:PROHIBIT(muscl_order == 2 .and. muscl_lim == dflt_int, "muscl_lim must be defined if using muscl_order = 2") - @:PROHIBIT(muscl_order /= 1 .and. muscl_lim < 1 .or. muscl_lim > 5, "muscl_lim must be 1,2,3,4, or 5") - end subroutine s_check_inputs_muscl - !> Checks constraints on Riemann solver parameters - impure subroutine s_check_inputs_riemann_solver - @:PROHIBIT(riemann_solver /= 2 .and. model_eqns == 3, "6-equation model (model_eqns = 3) requires riemann_solver = 2") - @:PROHIBIT(riemann_solver < 1 .or. riemann_solver > 5, "riemann_solver must be 1, 2, 3, 4 or 5") - @:PROHIBIT(all(wave_speeds /= (/dflt_int, 1, 2/)), "wave_speeds must be 1 or 2") - @:PROHIBIT(riemann_solver == 3 .and. wave_speeds /= dflt_int, "Exact Riemann (riemann_solver = 3) does not support wave_speeds") - @:PROHIBIT(all(avg_state /= (/dflt_int, 1, 2/)), "Unsupported value of avg_state") - @:PROHIBIT(riemann_solver /= 3 .and. riemann_solver /= 5 .and. wave_speeds == dflt_int, "wave_speeds must be set if riemann_solver != 3,5") - @:PROHIBIT(riemann_solver /= 3 .and. riemann_solver /= 5 .and. avg_state == dflt_int, "avg_state must be set if riemann_solver != 3,5") - @:PROHIBIT(all(low_Mach /= (/0, 1, 2/)), "low_Mach must be 0, 1 or 2") - @:PROHIBIT(riemann_solver /= 2 .and. low_Mach == 2, "low_Mach = 2 requires riemann_solver = 2") - @:PROHIBIT(low_Mach /= 0 .and. all(model_eqns /= (/2, 3/)), "low_Mach = 1 or 2 requires model_eqns = 2 or 3") - @:PROHIBIT(riemann_solver == 5 .and. cyl_coord .and. viscous, "Lax Friedrichs with cylincrical viscous flux not supported") - end subroutine s_check_inputs_riemann_solver - !> Checks constraints on geometry and precision impure subroutine s_check_inputs_geometry_precision ! Prevent spherical geometry in single precision @@ -167,252 +88,8 @@ contains if (.not. cfl_dt) then @:PROHIBIT(dt <= 0) end if - @:PROHIBIT(time_stepper < 1 .or. time_stepper > 3) end subroutine s_check_inputs_time_stepping - !> Checks constraints on parameters related to 6-equation model - impure subroutine s_check_inputs_model_eqns - @:PROHIBIT(model_eqns == 3 .and. avg_state /= 2, "6-equation model (model_eqns = 3) requires avg_state = 2") - @:PROHIBIT(model_eqns == 3 .and. wave_speeds /= 1, "6-equation model (model_eqns = 3) requires wave_speeds = 1") - end subroutine s_check_inputs_model_eqns - - !> Checks constraints for GRCBC - impure subroutine s_check_inputs_grcbc - #:for DIR in ['x', 'y', 'z'] - @:PROHIBIT(bc_${DIR}$%grcbc_in .and. (bc_${DIR}$%beg /= -7 .and. bc_${DIR}$%end /= -7), "Subsonic Inflow requires bc = -7") - @:PROHIBIT(bc_${DIR}$%grcbc_out .and. (bc_${DIR}$%beg /= -8 .and. bc_${DIR}$%end /= -8), "Subsonic Outflow requires bc = -8") - @:PROHIBIT(bc_${DIR}$%grcbc_vel_out .and. (bc_${DIR}$%beg /= -8 .and. bc_${DIR}$%end /= -8), "Subsonic Outflow requires bc = -8") - #:endfor - end subroutine s_check_inputs_grcbc - - !> Checks constraints on acoustic_source parameters - impure subroutine s_check_inputs_acoustic_src - - integer :: j, dim - character(len=5) :: jStr - - !! When it's obvious that the checks are only relevant if acoustic_source is enabled, - !! `acoustic_source .and.` is removed from the conditions for clarity. - !! `if (.not. acoustic_source) return` ensures equivalent behavior - if (.not. acoustic_source) return - - if (n == 0) then - dim = 1 - else if (p == 0) then - dim = 2 - else - dim = 3 - end if - - @:PROHIBIT(acoustic_source .and. num_source == dflt_int, "num_source must be specified for acoustic_source") - @:PROHIBIT(acoustic_source .and. num_source < 0, "num_source must be non-negative") - - do j = 1, num_source - call s_int_to_str(j, jStr) - - @:PROHIBIT(acoustic_source .and. acoustic(j)%support == dflt_int, & - "acoustic("//trim(jStr)//")%support must be specified for acoustic_source") - - @:PROHIBIT(dim == 1 .and. acoustic(j)%support /= 1, & - "Only acoustic("//trim(jStr)//")%support = 1 is allowed for 1D simulations") - @:PROHIBIT(dim == 1 .and. acoustic(j)%support == 1 .and. f_is_default(acoustic(j)%loc(1)), & - "acoustic("//trim(jStr)//")%loc(1) must be specified for acoustic("//trim(jStr)//")%support = 1") - @:PROHIBIT((dim == 2 .and. .not. cyl_coord) .and. (.not. any(acoustic(j)%support == (/2, 5, 9/))), & - "Only acoustic("//trim(jStr)//")%support = 2, 5, 6, 9, or 10 is allowed for 2D simulations") - @:PROHIBIT((dim == 2 .and. cyl_coord) .and. (.not. any(acoustic(j)%support == (/2, 6, 10/))), & - "Only acoustic("//trim(jStr)//")%support = 6 or 10 is allowed for 2D axisymmetric simulations") - @:PROHIBIT(dim == 2 .and. any(acoustic(j)%support == (/2, 5, 6, 9, 10/)) .and. & - (f_is_default(acoustic(j)%loc(1)) .or. f_is_default(acoustic(j)%loc(2))), & - "acoustic("//trim(jStr)//")%loc(1:2) must be specified for acoustic("//trim(jStr)//")%support = 2") - @:PROHIBIT(dim == 3 .and. (.not. any(acoustic(j)%support == (/3, 7, 11/))), & - "Only acoustic("//trim(jStr)//")%support = 3, 7, or 11 is allowed for 3D simulations") - @:PROHIBIT(dim == 3 .and. cyl_coord, & - "Acoustic source is not supported in 3D cylindrical simulations") - @:PROHIBIT(dim == 3 .and. acoustic(j)%support == 3 .and. & - (f_is_default(acoustic(j)%loc(1)) .or. f_is_default(acoustic(j)%loc(2))), & - "acoustic("//trim(jStr)//")%loc(1:2) must be specified for acoustic("//trim(jStr)//")%support = 3") - @:PROHIBIT(dim == 3 .and. any(acoustic(j)%support == (/7, 11/)) .and. & - (f_is_default(acoustic(j)%loc(1)) .or. & - f_is_default(acoustic(j)%loc(2)) .or. & - f_is_default(acoustic(j)%loc(3))), & - "acoustic("//trim(jStr)//")%loc(1:3) must be specified for acoustic("//trim(jStr)//")%support = 7 or 11") - - @:PROHIBIT(f_is_default(acoustic(j)%mag), & - "acoustic("//trim(jStr)//")%mag must be specified") - @:PROHIBIT(acoustic(j)%pulse == dflt_int, & - "acoustic("//trim(jStr)//")%pulse must be specified") - @:PROHIBIT(.not. any(acoustic(j)%pulse == (/1, 2, 3, 4/)), & - "Only acoustic("//trim(jStr)//")%pulse = 1, 2, 3 or 4 is allowed") - - @:PROHIBIT(any(acoustic(j)%pulse == (/1, 3/)) .and. & - (f_is_default(acoustic(j)%frequency) .eqv. f_is_default(acoustic(j)%wavelength)), & - "One and only one of acoustic("//trim(jStr)//")%frequency "// & - "or acoustic("//trim(jStr)//")%wavelength must be specified for pulse = 1 or 3") - @:PROHIBIT(acoustic(j)%pulse == 2 .and. & - (f_is_default(acoustic(j)%gauss_sigma_time) .eqv. f_is_default(acoustic(j)%gauss_sigma_dist)), & - "One and only one of acoustic("//trim(jStr)//")%gauss_sigma_time "// & - "or acoustic("//trim(jStr)//")%gauss_sigma_dist must be specified for pulse = 2") - @:PROHIBIT(acoustic(j)%pulse == 4 .and. acoustic(j)%bb_num_freq == dflt_int, & - "The number of broadband frequencies acoustic("//trim(jStr)//")%bb_num_freq must be specified for pulse = 4") - @:PROHIBIT(acoustic(j)%pulse == 4 .and. f_is_default(acoustic(j)%bb_bandwidth), & - "The broadband wave band width acoustic("//trim(jStr)//")%bb_bandwidth must be specified for pulse = 4") - @:PROHIBIT(acoustic(j)%pulse == 4 .and. f_is_default(acoustic(j)%bb_lowest_freq), & - "The broadband wave lower frequency bound acoustic("//trim(jStr)//")%bb_lowest_freq must be specified for pulse = 4") - - @:PROHIBIT(f_is_default(acoustic(j)%npulse), & - "acoustic("//trim(jStr)//")%npulse must be specified") - @:PROHIBIT(acoustic(j)%support >= 5 .and. (.not. f_is_integer(acoustic(j)%npulse)), & - "acoustic("//trim(jStr)//")%npulse must be an integer for support >= 5 (non-planar supports)") - @:PROHIBIT(acoustic(j)%npulse >= 5 .and. acoustic(j)%dipole, & - "acoustic("//trim(jStr)//")%dipole is not supported for support >= 5 (non-planar supports)") - @:PROHIBIT(acoustic(j)%support < 5 .and. f_is_default(acoustic(j)%dir), & - "acoustic("//trim(jStr)//")%dir must be specified for support < 5 (planer support)") - @:PROHIBIT(acoustic(j)%support == 1 .and. f_approx_equal(acoustic(j)%dir, 0._wp), & - "acoustic("//trim(jStr)//")dir must be non-zero for support = 1") - @:PROHIBIT(acoustic(j)%pulse == 2 .and. f_is_default(acoustic(j)%delay), & - "acoustic("//trim(jStr)//")%delay must be specified for pulse = 2 (Gaussian)") - @:PROHIBIT(acoustic(j)%pulse == 3 .and. acoustic(j)%support >= 5, & - "acoustic("//trim(jStr)//")%support >= 5 (Cylindrical or Spherical support) is not allowed for pulse = 3 (square wave)") - - @:PROHIBIT((acoustic(j)%support == 2 .or. acoustic(j)%support == 3) .and. f_is_default(acoustic(j)%length), & - "acoustic("//trim(jStr)//")%length must be specified for support = 2 or 3") - @:PROHIBIT((acoustic(j)%support == 2 .or. acoustic(j)%support == 3) .and. acoustic(j)%length <= 0._wp, & - "acoustic("//trim(jStr)//")%length must be positive for support = 2 or 3") - @:PROHIBIT(acoustic(j)%support == 3 .and. f_is_default(acoustic(j)%height), & - "acoustic("//trim(jStr)//")%height must be specified for support = 3") - @:PROHIBIT(acoustic(j)%support == 3 .and. acoustic(j)%height <= 0._wp, & - "acoustic("//trim(jStr)//")%height must be positive for support = 3") - - @:PROHIBIT(acoustic(j)%support >= 5 .and. f_is_default(acoustic(j)%foc_length), & - "acoustic("//trim(jStr)//")%foc_length must be specified for support >= 5 (non-planar supports)") - @:PROHIBIT(acoustic(j)%support >= 5 .and. acoustic(j)%foc_length <= 0._wp, & - "acoustic("//trim(jStr)//")%foc_length must be positive for support >= 5 (non-planar supports)") - @:PROHIBIT(acoustic(j)%support >= 5 .and. f_is_default(acoustic(j)%aperture), & - "acoustic("//trim(jStr)//")%aperture must be specified for support >= 5 (non-planar supports)") - @:PROHIBIT(acoustic(j)%support >= 5 .and. acoustic(j)%aperture <= 0._wp, & - "acoustic("//trim(jStr)//")%aperture must be positive for support >= 5 (non-planar supports)") - - @:PROHIBIT(any(acoustic(j)%support == (/9, 10, 11/)) .and. acoustic(j)%num_elements == dflt_int, & - "acoustic("//trim(jStr)//")%num_elements must be specified for support = 9, 10, or 11 (transducer array)") - @:PROHIBIT(any(acoustic(j)%support == (/9, 10, 11/)) .and. acoustic(j)%num_elements <= 0, & - "acoustic("//trim(jStr)//")%num_elements must be positive for support = 9, 10, or 11 (transducer array)") - @:PROHIBIT(acoustic(j)%element_on /= dflt_int .and. acoustic(j)%element_on < 0, & - "acoustic("//trim(jStr)//")%element_on must be non-negative for support = 9, 10, or 11 (transducer array)") - @:PROHIBIT(acoustic(j)%element_on /= dflt_int .and. acoustic(j)%element_on > acoustic(j)%num_elements, & - "acoustic("//trim(jStr)//")%element_on must be less than or equal to num_elements for support = 9, 10, or 11 (transducer array)") - @:PROHIBIT(any(acoustic(j)%support == (/9, 10/)) .and. f_is_default(acoustic(j)%element_spacing_angle), & - "acoustic("//trim(jStr)//")%element_spacing_angle must be specified for support = 9 or 10 (2D transducer array)") - @:PROHIBIT(any(acoustic(j)%support == (/9, 10/)) .and. acoustic(j)%element_spacing_angle < 0._wp, & - "acoustic("//trim(jStr)//")%element_spacing_angle must be non-negative for support = 9 or 10 (2D transducer array)") - @:PROHIBIT(acoustic(j)%support == 11 .and. f_is_default(acoustic(j)%element_polygon_ratio), & - "acoustic("//trim(jStr)//")%element_polygon_ratio must be specified for support = 11 (3D transducer array)") - @:PROHIBIT(acoustic(j)%support == 11 .and. acoustic(j)%element_polygon_ratio <= 0._wp, & - "acoustic("//trim(jStr)//")%element_polygon_ratio must be positive for support = 11 (3D transducer array)") - end do - - end subroutine s_check_inputs_acoustic_src - - !> Checks constraints on hypoelasticity parameters - impure subroutine s_check_inputs_hypoelasticity - @:PROHIBIT(hypoelasticity .and. riemann_solver /= 1, "hypoelasticity requires HLL Riemann solver (riemann_solver = 1)") - end subroutine - - !> Checks constraints on bubble parameters - impure subroutine s_check_inputs_bubbles_euler - @:PROHIBIT(bubbles_euler .and. bubbles_lagrange, "Activate only one of the bubble subgrid models") - @:PROHIBIT(bubbles_euler .and. riemann_solver /= 2, "Bubble modeling requires HLLC Riemann solver (riemann_solver = 2)") - @:PROHIBIT(bubbles_euler .and. avg_state /= 2, "Bubble modeling requires arithmetic average (avg_state = 2)") - @:PROHIBIT(bubbles_euler .and. model_eqns == 2 .and. bubble_model == 1, & - "The 5-equation bubbly flow model does not support bubble_model = 1 (Gilmore)") - end subroutine s_check_inputs_bubbles_euler - - !> Checks constraints on adaptive time stepping parameters (adap_dt) - impure subroutine s_check_inputs_adapt_dt - @:PROHIBIT(adap_dt .and. time_stepper /= 3, "adapt_dt requires Runge-Kutta 3 (time_stepper = 3)") - @:PROHIBIT(adap_dt .and. qbmm) - @:PROHIBIT(adap_dt .and. (.not. polytropic) .and. (.not. bubbles_lagrange)) - @:PROHIBIT(adap_dt .and. (.not. adv_n) .and. (.not. bubbles_lagrange)) - end subroutine s_check_inputs_adapt_dt - - !> Checks constraints on alternative sound speed parameters (alt_soundspeed) - impure subroutine s_check_inputs_alt_soundspeed - @:PROHIBIT(alt_soundspeed .and. model_eqns /= 2, "5-equation model (model_eqns = 2) is required for alt_soundspeed") - @:PROHIBIT(alt_soundspeed .and. riemann_solver /= 2, "alt_soundspeed requires HLLC Riemann solver (riemann_solver = 2)") - @:PROHIBIT(alt_soundspeed .and. num_fluids /= 2 .and. num_fluids /= 3) - end subroutine s_check_inputs_alt_soundspeed - - !> Checks constraints on viscosity parameters (fluid_pp(i)%Re(1:2)) - !! of the stiffened gas equation of state - impure subroutine s_check_inputs_stiffened_eos_viscosity - character(len=5) :: iStr, jStr - integer :: i, j - - do i = 1, num_fluids - do j = 1, 2 - call s_int_to_str(j, jStr) - @:PROHIBIT((.not. f_is_default(fluid_pp(i)%Re(j))) .and. fluid_pp(i)%Re(j) <= 0._wp, & - "fluid_pp("//trim(iStr)//")%"// "Re("//trim(jStr)//") must be positive.") - @:PROHIBIT(model_eqns == 1 .and. (.not. f_is_default(fluid_pp(i)%Re(j))), & - "model_eqns = 1 does not support fluid_pp("//trim(iStr)//")%"// "Re("//trim(jStr)//")") - @:PROHIBIT(i > num_fluids .and. (.not. f_is_default(fluid_pp(i)%Re(j))), & - "First index ("//trim(iStr)//") of fluid_pp("//trim(iStr)//")%"// "Re("//trim(jStr)//") exceeds num_fluids") - if (.not. igr) then - @:PROHIBIT(weno_order == 1 .and. (.not. weno_avg) .and. (.not. f_is_default(fluid_pp(i)%Re(j))), & - "weno_order = 1 without weno_avg does not support fluid_pp("//trim(iStr)//")%"// "Re("//trim(jStr)//")") - end if - end do - @:PROHIBIT(.not. f_is_default(fluid_pp(i)%Re(1)) .and. .not. viscous, & - "Re(1) is specified, but viscous is not set to true") - @:PROHIBIT(.not. f_is_default(fluid_pp(i)%Re(2)) .and. .not. viscous, & - "Re(2) is specified, but viscous is not set to true") - @:PROHIBIT(f_is_default(fluid_pp(i)%Re(1)) .and. viscous, & - "Re(1) is not specified, but viscous is set to true") - end do - - end subroutine s_check_inputs_stiffened_eos_viscosity - - !> Checks constraints on body forces parameters (bf_x[y,z], etc.) - impure subroutine s_check_inputs_body_forces - #:for DIR in ['x', 'y', 'z'] - @:PROHIBIT(bf_${DIR}$ .and. f_is_default(k_${DIR}$), "k_${DIR}$ must be specified if bf_${DIR}$ is true") - @:PROHIBIT(bf_${DIR}$ .and. f_is_default(w_${DIR}$), "w_${DIR}$ must be specified if bf_${DIR}$ is true") - @:PROHIBIT(bf_${DIR}$ .and. f_is_default(p_${DIR}$), "p_${DIR}$ must be specified if bf_${DIR}$ is true") - @:PROHIBIT(bf_${DIR}$ .and. f_is_default(g_${DIR}$), "g_${DIR}$ must be specified if bf_${DIR}$ is true") - #:endfor - end subroutine s_check_inputs_body_forces - - !> Checks constraints on lagrangian bubble parameters - impure subroutine s_check_inputs_bubbles_lagrange - @:PROHIBIT(bubbles_lagrange .and. file_per_process, "file_per_process must be false for bubbles_lagrange") - @:PROHIBIT(bubbles_lagrange .and. n==0, "bubbles_lagrange accepts 2D and 3D simulations only") - @:PROHIBIT(bubbles_lagrange .and. model_eqns==3, "The 6-equation flow model does not support bubbles_lagrange") - @:PROHIBIT(bubbles_lagrange .and. lag_params%cluster_type>=2 .and. lag_params%smooth_type/=1, "cluster_type=2 requires smooth_type=1") - end subroutine s_check_inputs_bubbles_lagrange - - !> Checks constraints on continuum damage model parameters - impure subroutine s_check_inputs_continuum_damage - @:PROHIBIT(cont_damage .and. f_is_default(tau_star)) - @:PROHIBIT(cont_damage .and. f_is_default(cont_damage_s)) - @:PROHIBIT(cont_damage .and. f_is_default(alpha_bar)) - end subroutine s_check_inputs_continuum_damage - - !> Checks miscellaneous constraints, - !! including constraints on probe_wrt and integral_wrt - impure subroutine s_check_inputs_misc - @:PROHIBIT(probe_wrt .and. fd_order == dflt_int, "fd_order must be specified for probe_wrt") - @:PROHIBIT(integral_wrt .and. (.not. bubbles_euler)) - end subroutine s_check_inputs_misc - - impure subroutine s_check_inputs_mhd - @:PROHIBIT(mhd .and. (riemann_solver /= 1 .and. riemann_solver /= 4), & - "MHD simulations require riemann_solver = 1 (HLL) or riemann_solver = 4 (HLLD)") - @:PROHIBIT(riemann_solver == 4 .and. .not. mhd, "HLLD is only available for MHD simulations") - @:PROHIBIT(riemann_solver == 4 .and. relativity, "HLLD is not available for RMHD") - @:PROHIBIT(powell .and. .not. mhd) - @:PROHIBIT(powell .and. n == 0, "Powell's method is not supported for 1D simulations") - @:PROHIBIT(powell .and. fd_order == dflt_int, "fd_order must be set if Powell's method is enabled") - end subroutine s_check_inputs_mhd - impure subroutine s_check_inputs_nvidia_uvm #ifdef __NVCOMPILER_GPU_UNIFIED_MEM @:PROHIBIT(nv_uvm_igr_temps_on_gpu > 3 .or. nv_uvm_igr_temps_on_gpu < 0, & diff --git a/toolchain/mfc/case_validator.py b/toolchain/mfc/case_validator.py new file mode 100644 index 000000000..4eea0e294 --- /dev/null +++ b/toolchain/mfc/case_validator.py @@ -0,0 +1,1688 @@ +""" +MFC Case Parameter Constraint Validator + +Validates inter-parameter dependencies and constraints before running +the Fortran codes. This catches configuration errors early and provides +user-friendly error messages. + +Based on the constraints enforced in: +- src/common/m_checker_common.fpp +- src/pre_process/m_checker.fpp +- src/simulation/m_checker.fpp +- src/post_process/m_checker.fpp +""" +# pylint: disable=too-many-lines +# Justification: Comprehensive validator covering all MFC parameter constraints + +from typing import Dict, Any, List +from .common import MFCException + + +class CaseConstraintError(MFCException): + """Exception raised when case parameters violate constraints""" + + +class CaseValidator: # pylint: disable=too-many-public-methods + """Validates MFC case parameter constraints""" + + def __init__(self, params: Dict[str, Any]): + self.params = params + self.errors: List[str] = [] + + def get(self, key: str, default=None): + """Get parameter value with default""" + return self.params.get(key, default) + + def is_set(self, key: str) -> bool: + """Check if parameter is set (not None and present)""" + return key in self.params and self.params[key] is not None + + def prohibit(self, condition: bool, message: str): + """Assert that condition is False, otherwise add error""" + if condition: + self.errors.append(message) + + # =================================================================== + # Common Checks (All Stages) + # =================================================================== + + def check_simulation_domain(self): + """Checks constraints on dimensionality and number of cells""" + m = self.get('m') + n = self.get('n', 0) + p = self.get('p', 0) + cyl_coord = self.get('cyl_coord', 'F') == 'T' + + self.prohibit(m is None, "m must be set") + self.prohibit(m is not None and m <= 0, "m must be positive") + self.prohibit(n is not None and n < 0, "n must be non-negative") + self.prohibit(p is not None and p < 0, "p must be non-negative") + self.prohibit(cyl_coord and p is not None and p > 0 and p % 2 == 0, + "p must be odd for cylindrical coordinates") + self.prohibit(n is not None and p is not None and n == 0 and p > 0, + "p must be 0 if n = 0") + + def check_model_eqns_and_num_fluids(self): + """Checks constraints on model equations and number of fluids""" + model_eqns = self.get('model_eqns') + num_fluids = self.get('num_fluids') + mpp_lim = self.get('mpp_lim', 'F') == 'T' + cyl_coord = self.get('cyl_coord', 'F') == 'T' + p = self.get('p', 0) + + self.prohibit(model_eqns is not None and model_eqns not in [1, 2, 3, 4], + "model_eqns must be 1, 2, 3, or 4") + self.prohibit(num_fluids is not None and num_fluids < 1, + "num_fluids must be positive") + self.prohibit(model_eqns == 1 and num_fluids is not None, + "num_fluids is not supported for model_eqns = 1") + self.prohibit(model_eqns == 2 and num_fluids is None, + "5-equation model (model_eqns = 2) requires num_fluids to be set") + self.prohibit(model_eqns == 3 and num_fluids is None, + "6-equation model (model_eqns = 3) requires num_fluids to be set") + self.prohibit(model_eqns == 4 and num_fluids is None, + "4-equation model (model_eqns = 4) requires num_fluids to be set") + self.prohibit(model_eqns == 1 and mpp_lim, + "model_eqns = 1 does not support mpp_lim") + self.prohibit(num_fluids == 1 and mpp_lim, + "num_fluids = 1 does not support mpp_lim") + self.prohibit(model_eqns == 3 and cyl_coord and p != 0, + "6-equation model (model_eqns = 3) does not support cylindrical coordinates (cyl_coord = T and p != 0)") + + def check_igr(self): + """Checks constraints regarding IGR order""" + igr = self.get('igr', 'F') == 'T' + + if not igr: + return + + igr_order = self.get('igr_order') + m = self.get('m', 0) + n = self.get('n', 0) + p = self.get('p', 0) + + self.prohibit(igr_order not in [None, 3, 5], + "igr_order must be 3 or 5") + if igr_order: + self.prohibit(m + 1 < igr_order, + f"m must be at least igr_order - 1 (= {igr_order - 1})") + self.prohibit(n is not None and n > 0 and n + 1 < igr_order, + f"n must be at least igr_order - 1 (= {igr_order - 1})") + self.prohibit(p is not None and p > 0 and p + 1 < igr_order, + f"p must be at least igr_order - 1 (= {igr_order - 1})") + + def check_weno(self): + """Checks constraints regarding WENO order""" + recon_type = self.get('recon_type', 1) + + # WENO_TYPE = 1 + if recon_type != 1: + return + + weno_order = self.get('weno_order') + m = self.get('m', 0) + n = self.get('n', 0) + p = self.get('p', 0) + + if weno_order is None: + return + + self.prohibit(weno_order not in [1, 3, 5, 7], + "weno_order must be 1, 3, 5, or 7") + self.prohibit(m + 1 < weno_order, + f"m must be at least weno_order - 1 (= {weno_order - 1})") + self.prohibit(n is not None and n > 0 and n + 1 < weno_order, + f"For 2D simulation, n must be at least weno_order - 1 (= {weno_order - 1})") + self.prohibit(p is not None and p > 0 and p + 1 < weno_order, + f"For 3D simulation, p must be at least weno_order - 1 (= {weno_order - 1})") + + def check_muscl(self): + """Check constraints regarding MUSCL order""" + recon_type = self.get('recon_type', 1) + + # MUSCL_TYPE = 2 + if recon_type != 2: + return + + muscl_order = self.get('muscl_order') + m = self.get('m', 0) + n = self.get('n', 0) + p = self.get('p', 0) + + if muscl_order is None: + return + + self.prohibit(muscl_order not in [1, 2], + "muscl_order must be 1 or 2") + self.prohibit(m + 1 < muscl_order, + f"m must be at least muscl_order - 1 (= {muscl_order - 1})") + self.prohibit(n is not None and n > 0 and n + 1 < muscl_order, + f"For 2D simulation, n must be at least muscl_order - 1 (= {muscl_order - 1})") + self.prohibit(p is not None and p > 0 and p + 1 < muscl_order, + f"For 3D simulation, p must be at least muscl_order - 1 (= {muscl_order - 1})") + + def check_boundary_conditions(self): # pylint: disable=too-many-locals + """Checks constraints on boundary conditions""" + cyl_coord = self.get('cyl_coord', 'F') == 'T' + m = self.get('m', 0) + n = self.get('n', 0) + p = self.get('p', 0) + + for dir, var in [('x', 'm'), ('y', 'n'), ('z', 'p')]: + var_val = {'m': m, 'n': n, 'p': p}[var] + + for bound in ['beg', 'end']: + bc_key = f'bc_{dir}%{bound}' + bc_val = self.get(bc_key) + + self.prohibit(var_val is not None and var_val == 0 and bc_val is not None, + f"{bc_key} is not supported for {var} = 0") + self.prohibit(var_val is not None and var_val > 0 and bc_val is None, + f"{var} != 0 but {bc_key} is not set") + + # Check periodicity matches + beg_bc = self.get(f'bc_{dir}%beg') + end_bc = self.get(f'bc_{dir}%end') + if beg_bc is not None and end_bc is not None: + self.prohibit((beg_bc == -1 and end_bc != -1) or (end_bc == -1 and beg_bc != -1), + f"bc_{dir}%beg and bc_{dir}%end must be both periodic (= -1) or both non-periodic") + + # Range check (skip for cylindrical y/z) + skip_check = cyl_coord and dir in ['y', 'z'] + for bound in ['beg', 'end']: + bc_key = f'bc_{dir}%{bound}' + bc_val = self.get(bc_key) + + if not skip_check and bc_val is not None: + self.prohibit(bc_val > -1 or bc_val < -17, + f"{bc_key} must be between -1 and -17") + self.prohibit(bc_val == -14 and not cyl_coord, + f"{bc_key} must not be -14 (BC_AXIS) for non-cylindrical coordinates") + + # Check BC_NULL is not used + for dir in ['x', 'y', 'z']: + for bound in ['beg', 'end']: + bc_val = self.get(f'bc_{dir}%{bound}') + self.prohibit(bc_val == -13, + "Boundary condition -13 (BC_NULL) is not supported") + + # Cylindrical specific checks + if cyl_coord: + self.prohibit(n is not None and n == 0, "n must be positive (2D or 3D) for cylindrical coordinates") + bc_y_beg = self.get('bc_y%beg') + bc_y_end = self.get('bc_y%end') + bc_z_beg = self.get('bc_z%beg') + bc_z_end = self.get('bc_z%end') + + self.prohibit(p is not None and p == 0 and bc_y_beg != -2, + "bc_y%beg must be -2 (BC_REFLECTIVE) for 2D cylindrical coordinates (p = 0)") + self.prohibit(p is not None and p > 0 and bc_y_beg != -14, + "bc_y%beg must be -14 (BC_AXIS) for 3D cylindrical coordinates (p > 0)") + + if bc_y_end is not None: + self.prohibit(bc_y_end > -1 or bc_y_end < -17, + "bc_y%end must be between -1 and -17") + self.prohibit(bc_y_end == -14, + "bc_y%end must not be -14 (BC_AXIS)") + + # 3D cylindrical + if p is not None and p > 0: + self.prohibit(bc_z_beg is not None and bc_z_beg not in [-1, -2], + "bc_z%beg must be -1 (periodic) or -2 (reflective) for 3D cylindrical coordinates") + self.prohibit(bc_z_end is not None and bc_z_end not in [-1, -2], + "bc_z%end must be -1 (periodic) or -2 (reflective) for 3D cylindrical coordinates") + + def check_bubbles_euler(self): + """Checks constraints on bubble parameters""" + bubbles_euler = self.get('bubbles_euler', 'F') == 'T' + + if not bubbles_euler: + return + + nb = self.get('nb') + polydisperse = self.get('polydisperse', 'F') == 'T' + polytropic = self.get('polytropic', 'F') == 'T' + R0ref = self.get('R0ref') + thermal = self.get('thermal') + model_eqns = self.get('model_eqns') + cyl_coord = self.get('cyl_coord', 'F') == 'T' + rhoref = self.get('rhoref') + pref = self.get('pref') + num_fluids = self.get('num_fluids') + + self.prohibit(nb is None or nb < 1, + "The Ensemble-Averaged Bubble Model requires nb >= 1") + self.prohibit(polydisperse and nb == 1, + "Polydisperse bubble dynamics requires nb > 1") + self.prohibit(polydisperse and nb is not None and nb % 2 == 0, + "nb must be odd for polydisperse bubbles") + self.prohibit(not polytropic and R0ref is None, + "R0ref must be set if using bubbles_euler with polytropic = F") + self.prohibit(thermal is not None and thermal > 3, + "thermal must be <= 3") + self.prohibit(model_eqns == 3, + "Bubble models untested with 6-equation model (model_eqns = 3)") + self.prohibit(model_eqns == 1, + "Bubble models untested with pi-gamma model (model_eqns = 1)") + self.prohibit(model_eqns == 4 and rhoref is None, + "rhoref must be set if using bubbles_euler with model_eqns = 4") + self.prohibit(model_eqns == 4 and pref is None, + "pref must be set if using bubbles_euler with model_eqns = 4") + self.prohibit(model_eqns == 4 and num_fluids != 1, + "4-equation model (model_eqns = 4) is single-component and requires num_fluids = 1") + self.prohibit(cyl_coord, + "Bubble models untested in cylindrical coordinates") + + def check_qbmm_and_polydisperse(self): + """Checks constraints on QBMM and polydisperse bubble parameters""" + polydisperse = self.get('polydisperse', 'F') == 'T' + bubbles_euler = self.get('bubbles_euler', 'F') == 'T' + poly_sigma = self.get('poly_sigma') + qbmm = self.get('qbmm', 'F') == 'T' + nnode = self.get('nnode') + + self.prohibit(polydisperse and not bubbles_euler, + "Polydisperse bubble modeling requires the bubbles_euler flag to be set") + self.prohibit(polydisperse and poly_sigma is None, + "Polydisperse bubble modeling requires poly_sigma to be set") + self.prohibit(polydisperse and poly_sigma is not None and poly_sigma <= 0, + "poly_sigma must be positive") + self.prohibit(qbmm and not bubbles_euler, + "QBMM requires the bubbles_euler flag to be set") + self.prohibit(qbmm and nnode is not None and nnode != 4, + "QBMM requires nnode = 4") + + def check_adv_n(self): + """Checks constraints on adv_n flag""" + adv_n = self.get('adv_n', 'F') == 'T' + bubbles_euler = self.get('bubbles_euler', 'F') == 'T' + num_fluids = self.get('num_fluids') + qbmm = self.get('qbmm', 'F') == 'T' + + if not adv_n: + return + + self.prohibit(not bubbles_euler, + "adv_n requires bubbles_euler to be enabled") + self.prohibit(num_fluids != 1, + "adv_n requires num_fluids = 1") + self.prohibit(qbmm, + "adv_n is not compatible with qbmm") + + def check_hypoelasticity(self): + """Checks constraints on hypoelasticity parameters""" + hypoelasticity = self.get('hypoelasticity', 'F') == 'T' + model_eqns = self.get('model_eqns') + riemann_solver = self.get('riemann_solver') + + if not hypoelasticity: + return + + self.prohibit(model_eqns is not None and model_eqns != 2, + "hypoelasticity requires model_eqns = 2") + self.prohibit(riemann_solver is not None and riemann_solver != 1, + "hypoelasticity requires HLL Riemann solver (riemann_solver = 1)") + + def check_phase_change(self): + """Checks constraints on phase change parameters""" + relax = self.get('relax', 'F') == 'T' + relax_model = self.get('relax_model') + model_eqns = self.get('model_eqns') + palpha_eps = self.get('palpha_eps') + ptgalpha_eps = self.get('ptgalpha_eps') + + if not relax: + return + + self.prohibit(model_eqns is not None and model_eqns != 3, + "phase change (relax) requires model_eqns = 3") + self.prohibit(relax_model is not None and (relax_model < 0 or relax_model > 6), + "relax_model must be between 0 and 6") + self.prohibit(palpha_eps is not None and palpha_eps <= 0, + "palpha_eps must be positive") + self.prohibit(palpha_eps is not None and palpha_eps >= 1, + "palpha_eps must be less than 1") + self.prohibit(ptgalpha_eps is not None and ptgalpha_eps <= 0, + "ptgalpha_eps must be positive") + self.prohibit(ptgalpha_eps is not None and ptgalpha_eps >= 1, + "ptgalpha_eps must be less than 1") + + def check_ibm(self): + """Checks constraints on Immersed Boundaries parameters""" + ib = self.get('ib', 'F') == 'T' + n = self.get('n', 0) + num_ibs = self.get('num_ibs', 0) + + self.prohibit(ib and n <= 0, + "Immersed Boundaries do not work in 1D (requires n > 0)") + self.prohibit(ib and (num_ibs <= 0 or num_ibs > 10), + "num_ibs must be between 1 and num_patches_max (10)") + self.prohibit(not ib and num_ibs > 0, + "num_ibs is set, but ib is not enabled") + + def check_stiffened_eos(self): + """Checks constraints on stiffened equation of state fluids parameters""" + num_fluids = self.get('num_fluids') + model_eqns = self.get('model_eqns') + bubbles_euler = self.get('bubbles_euler', 'F') == 'T' + + if num_fluids is None: + return + + # Allow one extra fluid property slot when using bubbles_euler with single-component flows + bub_fac = 1 if (bubbles_euler and num_fluids == 1) else 0 + + for i in range(1, num_fluids + 1 + bub_fac): + gamma = self.get(f'fluid_pp({i})%gamma') + pi_inf = self.get(f'fluid_pp({i})%pi_inf') + cv = self.get(f'fluid_pp({i})%cv') + + # Positivity checks + if gamma is not None: + self.prohibit(gamma <= 0, + f"fluid_pp({i})%gamma must be positive") + if pi_inf is not None: + self.prohibit(pi_inf < 0, + f"fluid_pp({i})%pi_inf must be non-negative") + if cv is not None: + self.prohibit(cv < 0, + f"fluid_pp({i})%cv must be positive") + + # Model-specific support + if model_eqns == 1: + self.prohibit(gamma is not None, + f"model_eqns = 1 does not support fluid_pp({i})%gamma") + self.prohibit(pi_inf is not None, + f"model_eqns = 1 does not support fluid_pp({i})%pi_inf") + + def check_surface_tension(self): + """Checks constraints on surface tension""" + surface_tension = self.get('surface_tension', 'F') == 'T' + sigma = self.get('sigma') + model_eqns = self.get('model_eqns') + num_fluids = self.get('num_fluids') + + if not surface_tension and sigma is None: + return + + self.prohibit(surface_tension and sigma is None, + "sigma must be set if surface_tension is enabled") + self.prohibit(surface_tension and sigma is not None and sigma < 0, + "sigma must be greater than or equal to zero") + self.prohibit(sigma is not None and not surface_tension, + "sigma is set but surface_tension is not enabled") + self.prohibit(surface_tension and model_eqns not in [2, 3], + "The surface tension model requires model_eqns = 2 or model_eqns = 3") + self.prohibit(surface_tension and num_fluids != 2, + "The surface tension model requires num_fluids = 2") + + def check_mhd(self): + """Checks constraints on MHD parameters""" + mhd = self.get('mhd', 'F') == 'T' + num_fluids = self.get('num_fluids') + model_eqns = self.get('model_eqns') + relativity = self.get('relativity', 'F') == 'T' + Bx0 = self.get('Bx0') + n = self.get('n', 0) + + self.prohibit(mhd and num_fluids != 1, + "MHD is only available for single-component flows (num_fluids = 1)") + self.prohibit(mhd and model_eqns != 2, + "MHD is only available for the 5-equation model (model_eqns = 2)") + self.prohibit(relativity and not mhd, + "relativity requires mhd to be enabled") + self.prohibit(Bx0 is not None and not mhd, + "Bx0 must not be set if MHD is not enabled") + self.prohibit(mhd and n is not None and n == 0 and Bx0 is None, + "Bx0 must be set in 1D MHD simulations") + self.prohibit(mhd and n is not None and n > 0 and Bx0 is not None, + "Bx0 must not be set in 2D/3D MHD simulations") + + # =================================================================== + # Simulation-Specific Checks + # =================================================================== + + def check_riemann_solver(self): + """Checks constraints on Riemann solver (simulation only)""" + riemann_solver = self.get('riemann_solver') + model_eqns = self.get('model_eqns') + wave_speeds = self.get('wave_speeds') + avg_state = self.get('avg_state') + low_Mach = self.get('low_Mach', 0) + cyl_coord = self.get('cyl_coord', 'F') == 'T' + viscous = self.get('viscous', 'F') == 'T' + + if riemann_solver is None: + return + + self.prohibit(riemann_solver < 1 or riemann_solver > 5, + "riemann_solver must be 1, 2, 3, 4 or 5") + self.prohibit(riemann_solver != 2 and model_eqns == 3, + "6-equation model (model_eqns = 3) requires riemann_solver = 2 (HLLC)") + self.prohibit(wave_speeds is not None and wave_speeds not in [1, 2], + "wave_speeds must be 1 or 2") + self.prohibit(riemann_solver == 3 and wave_speeds is not None, + "Exact Riemann (riemann_solver = 3) does not support wave_speeds") + self.prohibit(avg_state is not None and avg_state not in [1, 2], + "avg_state must be 1 or 2") + self.prohibit(riemann_solver not in [3, 5] and wave_speeds is None, + "wave_speeds must be set if riemann_solver != 3,5") + self.prohibit(riemann_solver not in [3, 5] and avg_state is None, + "avg_state must be set if riemann_solver != 3,5") + self.prohibit(low_Mach not in [0, 1, 2], + "low_Mach must be 0, 1, or 2") + self.prohibit(riemann_solver != 2 and low_Mach == 2, + "low_Mach = 2 requires riemann_solver = 2") + self.prohibit(low_Mach != 0 and model_eqns not in [2, 3], + "low_Mach = 1 or 2 requires model_eqns = 2 or 3") + self.prohibit(riemann_solver == 5 and cyl_coord and viscous, + "Lax Friedrichs with cylindrical viscous flux not supported") + + def check_time_stepping(self): + """Checks time stepping parameters (simulation/post-process)""" + cfl_dt = self.get('cfl_dt', 'F') == 'T' + time_stepper = self.get('time_stepper') + + # Check time_stepper bounds + self.prohibit(time_stepper is not None and (time_stepper < 1 or time_stepper > 3), + "time_stepper must be 1, 2, or 3") + + if cfl_dt: + cfl_target = self.get('cfl_target') + t_stop = self.get('t_stop') + t_save = self.get('t_save') + n_start = self.get('n_start') + + self.prohibit(cfl_target is not None and (cfl_target < 0 or cfl_target > 1), + "cfl_target must be between 0 and 1") + self.prohibit(t_stop is not None and t_stop <= 0, + "t_stop must be positive") + self.prohibit(t_save is not None and t_save <= 0, + "t_save must be positive") + self.prohibit(t_save is not None and t_stop is not None and t_save > t_stop, + "t_save must be <= t_stop") + self.prohibit(n_start is not None and n_start < 0, + "n_start must be non-negative") + else: + t_step_start = self.get('t_step_start') + t_step_stop = self.get('t_step_stop') + t_step_save = self.get('t_step_save') + dt = self.get('dt') + + self.prohibit(t_step_start is not None and t_step_start < 0, + "t_step_start must be non-negative") + self.prohibit(t_step_stop is not None and t_step_start is not None and t_step_stop <= t_step_start, + "t_step_stop must be > t_step_start") + self.prohibit(t_step_save is not None and t_step_stop is not None and t_step_start is not None and + t_step_save > t_step_stop - t_step_start, + "t_step_save must be <= (t_step_stop - t_step_start)") + self.prohibit(dt is not None and dt <= 0, + "dt must be positive") + + def check_finite_difference(self): + """Checks constraints on finite difference parameters""" + fd_order = self.get('fd_order') + + if fd_order is None: + return + + self.prohibit(fd_order not in [1, 2, 4], + "fd_order must be 1, 2, or 4") + + def check_weno_simulation(self): + """Checks WENO-specific constraints for simulation""" + weno_order = self.get('weno_order') + weno_eps = self.get('weno_eps') + wenoz = self.get('wenoz', 'F') == 'T' + wenoz_q = self.get('wenoz_q') + teno = self.get('teno', 'F') == 'T' + teno_CT = self.get('teno_CT') + mapped_weno = self.get('mapped_weno', 'F') == 'T' + mp_weno = self.get('mp_weno', 'F') == 'T' + weno_avg = self.get('weno_avg', 'F') == 'T' + model_eqns = self.get('model_eqns') + + # Check for multiple WENO schemes (regardless of weno_order being set) + num_schemes = sum([mapped_weno, wenoz, teno]) + self.prohibit(num_schemes >= 2, + "Only one of mapped_weno, wenoz, or teno can be set to true") + + # Early return if weno_order not set (other checks need it) + if weno_order is None: + return + + self.prohibit(weno_order != 1 and weno_eps is None, + "weno_order != 1 requires weno_eps to be set. A typical value is 1e-6") + self.prohibit(weno_eps is not None and weno_eps <= 0, + "weno_eps must be positive. A typical value is 1e-6") + self.prohibit(wenoz and weno_order == 7 and wenoz_q is None, + "wenoz at 7th order requires wenoz_q to be set (should be 2, 3, or 4)") + self.prohibit(wenoz and weno_order == 7 and wenoz_q is not None and wenoz_q not in [2, 3, 4], + "wenoz_q must be either 2, 3, or 4)") + self.prohibit(teno and teno_CT is None, + "teno requires teno_CT to be set. A typical value is 1e-6") + self.prohibit(teno and teno_CT is not None and teno_CT <= 0, + "teno_CT must be positive. A typical value is 1e-6") + + self.prohibit(weno_order == 1 and mapped_weno, + "mapped_weno is not compatible with weno_order = 1") + self.prohibit(weno_order == 1 and wenoz, + "wenoz is not compatible with weno_order = 1") + self.prohibit(weno_order in [1, 3] and teno, + "teno requires weno_order = 5 or 7") + self.prohibit(weno_order != 5 and mp_weno, + "mp_weno requires weno_order = 5") + self.prohibit(model_eqns == 1 and weno_avg, + "weno_avg is not compatible with model_eqns = 1") + + def check_muscl_simulation(self): + """Checks MUSCL-specific constraints for simulation""" + muscl_order = self.get('muscl_order') + muscl_lim = self.get('muscl_lim') + + if muscl_order is None: + return + + self.prohibit(muscl_order == 2 and muscl_lim is None, + "muscl_lim must be defined if using muscl_order = 2") + self.prohibit(muscl_lim is not None and (muscl_lim < 1 or muscl_lim > 5), + "muscl_lim must be 1, 2, 3, 4, or 5") + + def check_model_eqns_simulation(self): + """Checks model equation constraints specific to simulation""" + model_eqns = self.get('model_eqns') + avg_state = self.get('avg_state') + wave_speeds = self.get('wave_speeds') + + if model_eqns != 3: + return + + self.prohibit(avg_state is not None and avg_state != 2, + "6-equation model (model_eqns = 3) requires avg_state = 2") + self.prohibit(wave_speeds is not None and wave_speeds != 1, + "6-equation model (model_eqns = 3) requires wave_speeds = 1") + + def check_bubbles_euler_simulation(self): + """Checks bubble constraints specific to simulation""" + bubbles_euler = self.get('bubbles_euler', 'F') == 'T' + bubbles_lagrange = self.get('bubbles_lagrange', 'F') == 'T' + riemann_solver = self.get('riemann_solver') + avg_state = self.get('avg_state') + model_eqns = self.get('model_eqns') + bubble_model = self.get('bubble_model') + + self.prohibit(bubbles_euler and bubbles_lagrange, + "Activate only one of the bubble subgrid models (bubbles_euler or bubbles_lagrange)") + + if not bubbles_euler: + return + + self.prohibit(riemann_solver is not None and riemann_solver != 2, + "Bubble modeling requires HLLC Riemann solver (riemann_solver = 2)") + self.prohibit(avg_state is not None and avg_state != 2, + "Bubble modeling requires arithmetic average (avg_state = 2)") + self.prohibit(model_eqns == 2 and bubble_model == 1, + "The 5-equation bubbly flow model does not support bubble_model = 1 (Gilmore)") + + def check_body_forces(self): + """Checks constraints on body forces parameters""" + for dir in ['x', 'y', 'z']: + bf = self.get(f'bf_{dir}', 'F') == 'T' + + if not bf: + continue + + self.prohibit(self.get(f'k_{dir}') is None, + f"k_{dir} must be specified if bf_{dir} is true") + self.prohibit(self.get(f'w_{dir}') is None, + f"w_{dir} must be specified if bf_{dir} is true") + self.prohibit(self.get(f'p_{dir}') is None, + f"p_{dir} must be specified if bf_{dir} is true") + self.prohibit(self.get(f'g_{dir}') is None, + f"g_{dir} must be specified if bf_{dir} is true") + + def check_viscosity(self): + """Checks constraints on viscosity parameters""" + viscous = self.get('viscous', 'F') == 'T' + num_fluids = self.get('num_fluids') + model_eqns = self.get('model_eqns') + weno_order = self.get('weno_order') + weno_avg = self.get('weno_avg', 'F') == 'T' + igr = self.get('igr', 'F') == 'T' + + # If num_fluids is not set, check at least fluid 1 (for model_eqns=1) + if num_fluids is None: + num_fluids = 1 + + for i in range(1, num_fluids + 1): + Re1 = self.get(f'fluid_pp({i})%Re(1)') + Re2 = self.get(f'fluid_pp({i})%Re(2)') + + for j, Re_val in [(1, Re1), (2, Re2)]: + if Re_val is not None: + self.prohibit(Re_val <= 0, + f"fluid_pp({i})%Re({j}) must be positive") + self.prohibit(model_eqns == 1, + f"model_eqns = 1 does not support fluid_pp({i})%Re({j})") + if not igr: + self.prohibit(weno_order == 1 and not weno_avg, + f"weno_order = 1 without weno_avg does not support fluid_pp({i})%Re({j})") + self.prohibit(not viscous, + f"Re({j}) is specified, but viscous is not set to true") + + # Check Re(1) requirement + self.prohibit(Re1 is None and viscous, + f"viscous is set to true, but fluid_pp({i})%Re(1) is not specified") + + def check_mhd_simulation(self): + """Checks MHD constraints specific to simulation""" + mhd = self.get('mhd', 'F') == 'T' + riemann_solver = self.get('riemann_solver') + relativity = self.get('relativity', 'F') == 'T' + powell = self.get('powell', 'F') == 'T' + n = self.get('n', 0) + fd_order = self.get('fd_order') + + self.prohibit(mhd and riemann_solver is not None and riemann_solver not in [1, 4], + "MHD simulations require riemann_solver = 1 (HLL) or riemann_solver = 4 (HLLD)") + self.prohibit(riemann_solver == 4 and not mhd, + "HLLD (riemann_solver = 4) is only available for MHD simulations") + self.prohibit(riemann_solver == 4 and relativity, + "HLLD is not available for RMHD (relativity)") + self.prohibit(powell and not mhd, + "Powell's method requires mhd to be enabled") + self.prohibit(powell and n is not None and n == 0, + "Powell's method is not supported for 1D simulations") + self.prohibit(powell and fd_order is None, + "fd_order must be set if Powell's method is enabled") + + + def check_igr_simulation(self): # pylint: disable=too-many-locals + """Checks IGR constraints specific to simulation""" + igr = self.get('igr', 'F') == 'T' + + if not igr: + return + + num_igr_iters = self.get('num_igr_iters') + num_igr_warm_start_iters = self.get('num_igr_warm_start_iters') + igr_iter_solver = self.get('igr_iter_solver') + alf_factor = self.get('alf_factor') + model_eqns = self.get('model_eqns') + ib = self.get('ib', 'F') == 'T' + bubbles_euler = self.get('bubbles_euler', 'F') == 'T' + bubbles_lagrange = self.get('bubbles_lagrange', 'F') == 'T' + alt_soundspeed = self.get('alt_soundspeed', 'F') == 'T' + surface_tension = self.get('surface_tension', 'F') == 'T' + hypoelasticity = self.get('hypoelasticity', 'F') == 'T' + acoustic_source = self.get('acoustic_source', 'F') == 'T' + relax = self.get('relax', 'F') == 'T' + mhd = self.get('mhd', 'F') == 'T' + hyperelasticity = self.get('hyperelasticity', 'F') == 'T' + cyl_coord = self.get('cyl_coord', 'F') == 'T' + probe_wrt = self.get('probe_wrt', 'F') == 'T' + + self.prohibit(num_igr_iters is not None and num_igr_iters < 0, + "num_igr_iters must be greater than or equal to 0") + self.prohibit(num_igr_warm_start_iters is not None and num_igr_warm_start_iters < 0, + "num_igr_warm_start_iters must be greater than or equal to 0") + self.prohibit(igr_iter_solver is not None and igr_iter_solver not in [1, 2], + "igr_iter_solver must be 1 or 2") + self.prohibit(alf_factor is not None and alf_factor < 0, + "alf_factor must be non-negative") + self.prohibit(model_eqns is not None and model_eqns != 2, + "IGR only supports model_eqns = 2") + self.prohibit(ib, + "IGR does not support the immersed boundary method") + self.prohibit(bubbles_euler, + "IGR does not support Euler-Euler bubble models") + self.prohibit(bubbles_lagrange, + "IGR does not support Euler-Lagrange bubble models") + self.prohibit(alt_soundspeed, + "IGR does not support alt_soundspeed = T") + self.prohibit(surface_tension, + "IGR does not support surface tension") + self.prohibit(hypoelasticity, + "IGR does not support hypoelasticity") + self.prohibit(acoustic_source, + "IGR does not support acoustic sources") + self.prohibit(relax, + "IGR does not support phase change") + self.prohibit(mhd, + "IGR does not support magnetohydrodynamics") + self.prohibit(hyperelasticity, + "IGR does not support hyperelasticity") + self.prohibit(cyl_coord, + "IGR does not support cylindrical or axisymmetric coordinates") + self.prohibit(probe_wrt, + "IGR does not support probe writes") + + # Check BCs - IGR does not support characteristic BCs + # Characteristic BCs are BC_CHAR_SLIP_WALL (-5) through BC_CHAR_SUP_OUTFLOW (-12) + for dir in ['x', 'y', 'z']: + for bound in ['beg', 'end']: + bc = self.get(f'bc_{dir}%{bound}') + if bc is not None: + self.prohibit(-12 <= bc <= -5, + f"Characteristic boundary condition bc_{dir}%{bound} is not compatible with IGR") + + def check_acoustic_source(self): # pylint: disable=too-many-locals,too-many-branches,too-many-statements + """Checks acoustic source parameters (simulation)""" + acoustic_source = self.get('acoustic_source', 'F') == 'T' + + if not acoustic_source: + return + + num_source = self.get('num_source') + n = self.get('n', 0) + p = self.get('p', 0) + cyl_coord = self.get('cyl_coord', 'F') == 'T' + + # Determine dimensionality + if n is not None and n == 0: + dim = 1 + elif p is not None and p == 0: + dim = 2 + else: + dim = 3 + + self.prohibit(num_source is None, + "num_source must be specified for acoustic_source") + self.prohibit(num_source is not None and num_source < 0, + "num_source must be non-negative") + + if num_source is None or num_source <= 0: + return + + # Check each acoustic source + for j in range(1, num_source + 1): + jstr = str(j) + + support = self.get(f'acoustic({j})%support') + loc = [self.get(f'acoustic({j})%loc({i})') for i in range(1, 4)] + mag = self.get(f'acoustic({j})%mag') + pulse = self.get(f'acoustic({j})%pulse') + frequency = self.get(f'acoustic({j})%frequency') + wavelength = self.get(f'acoustic({j})%wavelength') + gauss_sigma_time = self.get(f'acoustic({j})%gauss_sigma_time') + gauss_sigma_dist = self.get(f'acoustic({j})%gauss_sigma_dist') + bb_num_freq = self.get(f'acoustic({j})%bb_num_freq') + bb_bandwidth = self.get(f'acoustic({j})%bb_bandwidth') + bb_lowest_freq = self.get(f'acoustic({j})%bb_lowest_freq') + npulse = self.get(f'acoustic({j})%npulse') + dipole = self.get(f'acoustic({j})%dipole', 'F') == 'T' + dir_val = self.get(f'acoustic({j})%dir') + delay = self.get(f'acoustic({j})%delay') + length = self.get(f'acoustic({j})%length') + height = self.get(f'acoustic({j})%height') + foc_length = self.get(f'acoustic({j})%foc_length') + aperture = self.get(f'acoustic({j})%aperture') + num_elements = self.get(f'acoustic({j})%num_elements') + element_on = self.get(f'acoustic({j})%element_on') + element_spacing_angle = self.get(f'acoustic({j})%element_spacing_angle') + element_polygon_ratio = self.get(f'acoustic({j})%element_polygon_ratio') + + self.prohibit(support is None, + f"acoustic({jstr})%support must be specified for acoustic_source") + + # Dimension-specific support checks (only if support was specified) + if support is not None: + if dim == 1: + self.prohibit(support != 1, + f"Only acoustic({jstr})%support = 1 is allowed for 1D simulations") + self.prohibit(support == 1 and loc[0] is None, + f"acoustic({jstr})%loc(1) must be specified for support = 1") + + elif dim == 2: + if cyl_coord: + self.prohibit(support not in [2, 6, 10], + f"Only acoustic({jstr})%support = 2, 6, or 10 is allowed for 2D axisymmetric") + else: + self.prohibit(support not in [2, 5, 6, 9, 10], + f"Only acoustic({jstr})%support = 2, 5, 6, 9, or 10 is allowed for 2D") + + if support in [2, 5, 6, 9, 10]: + self.prohibit(loc[0] is None or loc[1] is None, + f"acoustic({jstr})%loc(1:2) must be specified for support = {support}") + + elif dim == 3: + self.prohibit(support not in [3, 7, 11], + f"Only acoustic({jstr})%support = 3, 7, or 11 is allowed for 3D") + self.prohibit(cyl_coord, + "Acoustic source is not supported in 3D cylindrical simulations") + + if support == 3: + self.prohibit(loc[0] is None or loc[1] is None, + f"acoustic({jstr})%loc(1:2) must be specified for support = 3") + elif support in [7, 11]: + self.prohibit(loc[0] is None or loc[1] is None or loc[2] is None, + f"acoustic({jstr})%loc(1:3) must be specified for support = {support}") + + # Pulse parameters + self.prohibit(mag is None, + f"acoustic({jstr})%mag must be specified") + self.prohibit(pulse is None, + f"acoustic({jstr})%pulse must be specified") + self.prohibit(pulse is not None and pulse not in [1, 2, 3, 4], + f"Only acoustic({jstr})%pulse = 1, 2, 3, or 4 is allowed") + + # Pulse-specific requirements + if pulse in [1, 3]: + freq_set = frequency is not None + wave_set = wavelength is not None + self.prohibit(freq_set == wave_set, + f"One and only one of acoustic({jstr})%frequency or wavelength must be specified for pulse = {pulse}") + + if pulse == 2: + time_set = gauss_sigma_time is not None + dist_set = gauss_sigma_dist is not None + self.prohibit(time_set == dist_set, + f"One and only one of acoustic({jstr})%gauss_sigma_time or gauss_sigma_dist must be specified for pulse = 2") + self.prohibit(delay is None, + f"acoustic({jstr})%delay must be specified for pulse = 2 (Gaussian)") + + if pulse == 4: + self.prohibit(bb_num_freq is None, + f"acoustic({jstr})%bb_num_freq must be specified for pulse = 4") + self.prohibit(bb_bandwidth is None, + f"acoustic({jstr})%bb_bandwidth must be specified for pulse = 4") + self.prohibit(bb_lowest_freq is None, + f"acoustic({jstr})%bb_lowest_freq must be specified for pulse = 4") + + # npulse checks + self.prohibit(npulse is None, + f"acoustic({jstr})%npulse must be specified") + self.prohibit(support is not None and support >= 5 and npulse is not None and not isinstance(npulse, int), + f"acoustic({jstr})%npulse must be an integer for support >= 5 (non-planar)") + self.prohibit(npulse is not None and npulse >= 5 and dipole, + f"acoustic({jstr})%dipole is not supported for npulse >= 5") + self.prohibit(support is not None and support < 5 and dir_val is None, + f"acoustic({jstr})%dir must be specified for support < 5 (planar)") + self.prohibit(support == 1 and dir_val is not None and dir_val == 0, + f"acoustic({jstr})%dir must be non-zero for support = 1") + self.prohibit(pulse == 3 and support is not None and support >= 5, + f"acoustic({jstr})%support >= 5 is not allowed for pulse = 3 (square wave)") + + # Geometry checks + if support in [2, 3]: + self.prohibit(length is None, + f"acoustic({jstr})%length must be specified for support = {support}") + self.prohibit(length is not None and length <= 0, + f"acoustic({jstr})%length must be positive for support = {support}") + + if support == 3: + self.prohibit(height is None, + f"acoustic({jstr})%height must be specified for support = 3") + self.prohibit(height is not None and height <= 0, + f"acoustic({jstr})%height must be positive for support = 3") + + if support is not None and support >= 5: + self.prohibit(foc_length is None, + f"acoustic({jstr})%foc_length must be specified for support >= 5 (non-planar)") + self.prohibit(foc_length is not None and foc_length <= 0, + f"acoustic({jstr})%foc_length must be positive for support >= 5") + self.prohibit(aperture is None, + f"acoustic({jstr})%aperture must be specified for support >= 5 (non-planar)") + self.prohibit(aperture is not None and aperture <= 0, + f"acoustic({jstr})%aperture must be positive for support >= 5") + + # Transducer array checks + if support in [9, 10, 11]: + self.prohibit(num_elements is None, + f"acoustic({jstr})%num_elements must be specified for support = {support} (transducer array)") + self.prohibit(num_elements is not None and num_elements <= 0, + f"acoustic({jstr})%num_elements must be positive for support = {support}") + self.prohibit(element_on is not None and element_on < 0, + f"acoustic({jstr})%element_on must be non-negative for support = {support}") + self.prohibit(element_on is not None and num_elements is not None and element_on > num_elements, + f"acoustic({jstr})%element_on must be <= num_elements for support = {support}") + + if support in [9, 10]: + self.prohibit(element_spacing_angle is None, + f"acoustic({jstr})%element_spacing_angle must be specified for support = {support} (2D transducer)") + self.prohibit(element_spacing_angle is not None and element_spacing_angle < 0, + f"acoustic({jstr})%element_spacing_angle must be non-negative for support = {support}") + + if support == 11: + self.prohibit(element_polygon_ratio is None, + f"acoustic({jstr})%element_polygon_ratio must be specified for support = 11 (3D transducer)") + self.prohibit(element_polygon_ratio is not None and element_polygon_ratio <= 0, + f"acoustic({jstr})%element_polygon_ratio must be positive for support = 11") + + def check_adaptive_time_stepping(self): + """Checks adaptive time stepping parameters (simulation)""" + adap_dt = self.get('adap_dt', 'F') == 'T' + + if not adap_dt: + return + + time_stepper = self.get('time_stepper') + model_eqns = self.get('model_eqns') + polytropic = self.get('polytropic', 'F') == 'T' + bubbles_lagrange = self.get('bubbles_lagrange', 'F') == 'T' + qbmm = self.get('qbmm', 'F') == 'T' + adv_n = self.get('adv_n', 'F') == 'T' + + self.prohibit(time_stepper is not None and time_stepper != 3, + "adap_dt requires Runge-Kutta 3 (time_stepper = 3)") + self.prohibit(model_eqns == 1, + "adap_dt is not supported for model_eqns = 1") + self.prohibit(qbmm, + "adap_dt is not compatible with qbmm") + self.prohibit(not polytropic and not bubbles_lagrange, + "adap_dt requires polytropic = T or bubbles_lagrange = T") + self.prohibit(not adv_n and not bubbles_lagrange, + "adap_dt requires adv_n = T or bubbles_lagrange = T") + + def check_alt_soundspeed(self): + """Checks alternative sound speed parameters (simulation)""" + alt_soundspeed = self.get('alt_soundspeed', 'F') == 'T' + + if not alt_soundspeed: + return + + model_eqns = self.get('model_eqns') + bubbles_euler = self.get('bubbles_euler', 'F') == 'T' + avg_state = self.get('avg_state') + riemann_solver = self.get('riemann_solver') + num_fluids = self.get('num_fluids') + + self.prohibit(model_eqns is not None and model_eqns != 2, + "5-equation model (model_eqns = 2) is required for alt_soundspeed") + self.prohibit(bubbles_euler, + "alt_soundspeed is not compatible with bubbles_euler") + self.prohibit(avg_state is not None and avg_state != 2, + "alt_soundspeed requires avg_state = 2") + self.prohibit(riemann_solver is not None and riemann_solver != 2, + "alt_soundspeed requires HLLC Riemann solver (riemann_solver = 2)") + self.prohibit(num_fluids is not None and num_fluids not in [2, 3], + "alt_soundspeed requires num_fluids = 2 or 3") + + def check_bubbles_lagrange(self): + """Checks Lagrangian bubble parameters (simulation)""" + bubbles_lagrange = self.get('bubbles_lagrange', 'F') == 'T' + + if not bubbles_lagrange: + return + + n = self.get('n', 0) + file_per_process = self.get('file_per_process', 'F') == 'T' + model_eqns = self.get('model_eqns') + cluster_type = self.get('lag_params%cluster_type') + smooth_type = self.get('lag_params%smooth_type') + + self.prohibit(n is not None and n == 0, + "bubbles_lagrange accepts 2D and 3D simulations only") + self.prohibit(file_per_process, + "file_per_process must be false for bubbles_lagrange") + self.prohibit(model_eqns == 3, + "The 6-equation flow model does not support bubbles_lagrange") + self.prohibit(cluster_type is not None and cluster_type >= 2 and smooth_type != 1, + "cluster_type >= 2 requires smooth_type = 1") + + def check_continuum_damage(self): + """Checks continuum damage model parameters (simulation)""" + cont_damage = self.get('cont_damage', 'F') == 'T' + + if not cont_damage: + return + + tau_star = self.get('tau_star') + cont_damage_s = self.get('cont_damage_s') + alpha_bar = self.get('alpha_bar') + model_eqns = self.get('model_eqns') + + self.prohibit(tau_star is None, + "tau_star must be specified for cont_damage") + self.prohibit(cont_damage_s is None, + "cont_damage_s must be specified for cont_damage") + self.prohibit(alpha_bar is None, + "alpha_bar must be specified for cont_damage") + self.prohibit(model_eqns is not None and model_eqns != 2, + "cont_damage requires model_eqns = 2") + + def check_grcbc(self): + """Checks Generalized Relaxation Characteristics BC (simulation)""" + for dir in ['x', 'y', 'z']: + grcbc_in = self.get(f'bc_{dir}%grcbc_in', 'F') == 'T' + grcbc_out = self.get(f'bc_{dir}%grcbc_out', 'F') == 'T' + grcbc_vel_out = self.get(f'bc_{dir}%grcbc_vel_out', 'F') == 'T' + bc_beg = self.get(f'bc_{dir}%beg') + bc_end = self.get(f'bc_{dir}%end') + + if grcbc_in: + # Check if EITHER beg OR end is set to -7 + self.prohibit(bc_beg != -7 and bc_end != -7, + f"Subsonic Inflow (grcbc_in) requires bc_{dir}%beg = -7 or bc_{dir}%end = -7") + if grcbc_out: + # Check if EITHER beg OR end is set to -8 + self.prohibit(bc_beg != -8 and bc_end != -8, + f"Subsonic Outflow (grcbc_out) requires bc_{dir}%beg = -8 or bc_{dir}%end = -8") + if grcbc_vel_out: + self.prohibit(bc_beg != -8 and bc_end != -8, + f"Subsonic Outflow Velocity (grcbc_vel_out) requires bc_{dir}%beg = -8 or bc_{dir}%end = -8") + + def check_probe_integral_output(self): + """Checks probe and integral output requirements (simulation)""" + probe_wrt = self.get('probe_wrt', 'F') == 'T' + integral_wrt = self.get('integral_wrt', 'F') == 'T' + fd_order = self.get('fd_order') + bubbles_euler = self.get('bubbles_euler', 'F') == 'T' + + self.prohibit(probe_wrt and fd_order is None, + "fd_order must be specified for probe_wrt") + self.prohibit(integral_wrt and fd_order is None, + "fd_order must be specified for integral_wrt") + self.prohibit(integral_wrt and not bubbles_euler, + "integral_wrt requires bubbles_euler to be enabled") + + def check_hyperelasticity(self): + """Checks hyperelasticity constraints""" + hyperelasticity = self.get('hyperelasticity', 'F') == 'T' + + if not hyperelasticity: + return + + model_eqns = self.get('model_eqns') + + self.prohibit(model_eqns == 1, + "hyperelasticity is not supported for model_eqns = 1") + self.prohibit(model_eqns is not None and model_eqns > 3, + "hyperelasticity is not supported for model_eqns > 3") + + # =================================================================== + # Pre-Process Specific Checks + # =================================================================== + + def check_restart(self): + """Checks constraints on restart parameters (pre-process)""" + old_grid = self.get('old_grid', 'F') == 'T' + old_ic = self.get('old_ic', 'F') == 'T' + t_step_old = self.get('t_step_old') + num_patches = self.get('num_patches', 0) + + self.prohibit(not old_grid and old_ic, + "old_ic can only be enabled with old_grid enabled") + self.prohibit(old_grid and t_step_old is None, + "old_grid requires t_step_old to be set") + self.prohibit(num_patches < 0, + "num_patches must be non-negative") + self.prohibit(num_patches == 0 and t_step_old is None, + "num_patches must be positive for the non-restart case") + + def check_qbmm_pre_process(self): + """Checks QBMM constraints for pre-process""" + qbmm = self.get('qbmm', 'F') == 'T' + dist_type = self.get('dist_type') + rhoRV = self.get('rhoRV') + + if not qbmm: + return + + self.prohibit(dist_type is None, + "dist_type must be set if using QBMM") + self.prohibit(dist_type is not None and dist_type != 1 and rhoRV is not None and rhoRV > 0, + "rhoRV cannot be used with dist_type != 1") + + def check_parallel_io_pre_process(self): + """Checks parallel I/O constraints (pre-process)""" + parallel_io = self.get('parallel_io', 'F') == 'T' + down_sample = self.get('down_sample', 'F') == 'T' + igr = self.get('igr', 'F') == 'T' + p = self.get('p', 0) + file_per_process = self.get('file_per_process', 'F') == 'T' + m = self.get('m', 0) + n = self.get('n', 0) + + if down_sample: + self.prohibit(not parallel_io, + "down sample requires parallel_io = T") + self.prohibit(not igr, + "down sample requires igr = T") + self.prohibit(p == 0, + "down sample requires 3D (p > 0)") + self.prohibit(not file_per_process, + "down sample requires file_per_process = T") + if m is not None and m >= 0: + self.prohibit((m + 1) % 3 != 0, + "down sample requires m divisible by 3") + if n is not None and n >= 0: + self.prohibit((n + 1) % 3 != 0, + "down sample requires n divisible by 3") + if p is not None and p >= 0: + self.prohibit((p + 1) % 3 != 0, + "down sample requires p divisible by 3") + + def check_grid_stretching(self): # pylint: disable=too-many-branches + """Checks grid stretching constraints (pre-process)""" + loops_x = self.get('loops_x', 1) + loops_y = self.get('loops_y', 1) + stretch_y = self.get('stretch_y', 'F') == 'T' + stretch_z = self.get('stretch_z', 'F') == 'T' + old_grid = self.get('old_grid', 'F') == 'T' + n = self.get('n', 0) + p = self.get('p', 0) + cyl_coord = self.get('cyl_coord', 'F') == 'T' + + self.prohibit(loops_x < 1, + "loops_x must be at least 1") + self.prohibit(loops_y < 1, + "loops_y must be at least 1") + self.prohibit(stretch_y and n == 0, + "stretch_y requires n > 0") + self.prohibit(stretch_z and p == 0, + "stretch_z requires p > 0") + self.prohibit(stretch_z and cyl_coord, + "stretch_z is not compatible with cylindrical coordinates") + + for direction in ['x', 'y', 'z']: + stretch = self.get(f'stretch_{direction}', 'F') == 'T' + if not stretch: + continue + + a = self.get(f'a_{direction}') + coord_a = self.get(f'{direction}_a') + coord_b = self.get(f'{direction}_b') + + self.prohibit(old_grid, + f"old_grid and stretch_{direction} are incompatible") + self.prohibit(a is None, + f"a_{direction} must be set with stretch_{direction} enabled") + self.prohibit(coord_a is None, + f"{direction}_a must be set with stretch_{direction} enabled") + self.prohibit(coord_b is None, + f"{direction}_b must be set with stretch_{direction} enabled") + if coord_a is not None and coord_b is not None: + self.prohibit(coord_a >= coord_b, + f"{direction}_a must be less than {direction}_b with stretch_{direction} enabled") + + def check_perturb_density(self): + """Checks initial partial density perturbation constraints (pre-process)""" + perturb_flow = self.get('perturb_flow', 'F') == 'T' + perturb_flow_fluid = self.get('perturb_flow_fluid') + perturb_flow_mag = self.get('perturb_flow_mag') + perturb_sph = self.get('perturb_sph', 'F') == 'T' + perturb_sph_fluid = self.get('perturb_sph_fluid') + num_fluids = self.get('num_fluids') + + if perturb_flow: + self.prohibit(perturb_flow_fluid is None or perturb_flow_mag is None, + "perturb_flow_fluid and perturb_flow_mag must be set with perturb_flow = T") + else: + self.prohibit(perturb_flow_fluid is not None or perturb_flow_mag is not None, + "perturb_flow_fluid and perturb_flow_mag must not be set with perturb_flow = F") + + if num_fluids is not None and perturb_flow_fluid is not None: + self.prohibit(perturb_flow_fluid > num_fluids or perturb_flow_fluid < 0, + "perturb_flow_fluid must be between 0 and num_fluids") + + if perturb_sph: + self.prohibit(perturb_sph_fluid is None, + "perturb_sph_fluid must be set with perturb_sph = T") + else: + self.prohibit(perturb_sph_fluid is not None, + "perturb_sph_fluid must not be set with perturb_sph = F") + + if num_fluids is not None and perturb_sph_fluid is not None: + self.prohibit(perturb_sph_fluid > num_fluids or perturb_sph_fluid < 0, + "perturb_sph_fluid must be between 0 and num_fluids") + + def check_chemistry(self): + """Checks chemistry constraints (pre-process) + + Note: num_species is set automatically by Cantera at runtime when cantera_file + is provided. No static validation is performed here - chemistry will fail at + runtime if misconfigured. + """ + + def check_misc_pre_process(self): + """Checks miscellaneous pre-process constraints""" + mixlayer_vel_profile = self.get('mixlayer_vel_profile', 'F') == 'T' + mixlayer_perturb = self.get('mixlayer_perturb', 'F') == 'T' + elliptic_smoothing = self.get('elliptic_smoothing', 'F') == 'T' + elliptic_smoothing_iters = self.get('elliptic_smoothing_iters') + n = self.get('n', 0) + p = self.get('p', 0) + + self.prohibit(mixlayer_vel_profile and n == 0, + "mixlayer_vel_profile requires n > 0") + self.prohibit(mixlayer_perturb and p == 0, + "mixlayer_perturb requires p > 0") + if elliptic_smoothing and elliptic_smoothing_iters is not None: + self.prohibit(elliptic_smoothing_iters < 1, + "elliptic_smoothing_iters must be positive") + + def check_bc_patches(self): # pylint: disable=too-many-branches,too-many-statements + """Checks boundary condition patch geometry (pre-process)""" + num_bc_patches = self.get('num_bc_patches', 0) + num_bc_patches_max = self.get('num_bc_patches_max', 10) + + if num_bc_patches <= 0: + return + + self.prohibit(num_bc_patches > num_bc_patches_max, + f"num_bc_patches must be <= {num_bc_patches_max}") + + for i in range(1, num_bc_patches + 1): + geometry = self.get(f'patch_bc({i})%geometry') + bc_type = self.get(f'patch_bc({i})%type') + direction = self.get(f'patch_bc({i})%dir') + radius = self.get(f'patch_bc({i})%radius') + centroid = [self.get(f'patch_bc({i})%centroid({j})') for j in range(1, 4)] + length = [self.get(f'patch_bc({i})%length({j})') for j in range(1, 4)] + + if geometry is None: + continue + + # Line Segment BC (geometry = 1) + if geometry == 1: + self.prohibit(radius is not None, + f"Line Segment Patch {i} can't have radius defined") + if direction in [1, 2]: + self.prohibit(centroid[direction - 1] is not None or centroid[2] is not None, + f"Line Segment Patch {i} of Dir {direction} can't have centroid in Dir {direction} or 3") + self.prohibit(length[direction - 1] is not None or length[2] is not None, + f"Line Segment Patch {i} of Dir {direction} can't have length in Dir {direction} or 3") + + # Circle BC (geometry = 2) + elif geometry == 2: + self.prohibit(radius is None, + f"Circle Patch {i} must have radius defined") + self.prohibit(any(length_val is not None for length_val in length), + f"Circle Patch {i} can't have lengths defined") + if direction in [1, 2, 3]: + self.prohibit(centroid[direction - 1] is not None, + f"Circle Patch {i} of Dir {direction} can't have centroid in Dir {direction}") + + # Rectangle BC (geometry = 3) + elif geometry == 3: + self.prohibit(radius is not None, + f"Rectangle Patch {i} can't have radius defined") + if direction in [1, 2, 3]: + self.prohibit(centroid[direction - 1] is not None, + f"Rectangle Patch {i} of Dir {direction} can't have centroid in Dir {direction}") + self.prohibit(length[direction - 1] is not None, + f"Rectangle Patch {i} of Dir {direction} can't have length in Dir {direction}") + + # Check for incompatible BC types + if bc_type is not None: + # BC types -14 to -4, -1 (periodic), or < -17 (dirichlet) are incompatible with patches + self.prohibit((-14 <= bc_type <= -4) or bc_type == -1 or bc_type < -17, + f"Incompatible BC type for boundary condition patch {i}") + + # =================================================================== + # Post-Process Specific Checks + # =================================================================== + + def check_output_format(self): + """Checks output format parameters (post-process)""" + format = self.get('format') + precision = self.get('precision') + + if format is not None: + self.prohibit(format not in [1, 2], + "format must be 1 or 2") + + if precision is not None: + self.prohibit(precision not in [1, 2], + "precision must be 1 or 2") + + def check_vorticity(self): + """Checks vorticity parameters (post-process)""" + omega_wrt = [self.get(f'omega_wrt({i})', 'F') == 'T' for i in range(1, 4)] + n = self.get('n', 0) + p = self.get('p', 0) + fd_order = self.get('fd_order') + + self.prohibit(n is not None and n == 0 and any(omega_wrt), + "omega_wrt requires n > 0 (at least 2D)") + self.prohibit(p is not None and p == 0 and (omega_wrt[0] or omega_wrt[1]), + "omega_wrt(1) and omega_wrt(2) require p > 0 (3D)") + self.prohibit(any(omega_wrt) and fd_order is None, + "fd_order must be set for omega_wrt") + + def check_schlieren(self): + """Checks schlieren parameters (post-process)""" + schlieren_wrt = self.get('schlieren_wrt', 'F') == 'T' + n = self.get('n', 0) + fd_order = self.get('fd_order') + num_fluids = self.get('num_fluids') + + self.prohibit(n is not None and n == 0 and schlieren_wrt, + "schlieren_wrt requires n > 0 (at least 2D)") + self.prohibit(schlieren_wrt and fd_order is None, + "fd_order must be set for schlieren_wrt") + + if num_fluids is not None: + for i in range(1, num_fluids + 1): + schlieren_alpha = self.get(f'schlieren_alpha({i})') + if schlieren_alpha is not None: + self.prohibit(schlieren_alpha <= 0, + f"schlieren_alpha({i}) must be greater than zero") + self.prohibit(not schlieren_wrt, + f"schlieren_alpha({i}) should be set only with schlieren_wrt enabled") + + def check_partial_domain(self): # pylint: disable=too-many-locals + """Checks partial domain output constraints (post-process)""" + output_partial_domain = self.get('output_partial_domain', 'F') == 'T' + + if not output_partial_domain: + return + + format_val = self.get('format') + precision = self.get('precision') + flux_wrt = self.get('flux_wrt', 'F') == 'T' + heat_ratio_wrt = self.get('heat_ratio_wrt', 'F') == 'T' + pres_inf_wrt = self.get('pres_inf_wrt', 'F') == 'T' + c_wrt = self.get('c_wrt', 'F') == 'T' + schlieren_wrt = self.get('schlieren_wrt', 'F') == 'T' + qm_wrt = self.get('qm_wrt', 'F') == 'T' + liutex_wrt = self.get('liutex_wrt', 'F') == 'T' + ib = self.get('ib', 'F') == 'T' + omega_wrt = [self.get(f'omega_wrt({i})', 'F') == 'T' for i in range(1, 4)] + n = self.get('n', 0) + p = self.get('p', 0) + + self.prohibit(format_val == 1, + "output_partial_domain requires format = 2") + self.prohibit(precision == 1, + "output_partial_domain requires precision = 2") + self.prohibit(flux_wrt or heat_ratio_wrt or pres_inf_wrt or c_wrt or + schlieren_wrt or qm_wrt or liutex_wrt or ib or any(omega_wrt), + "output_partial_domain is incompatible with certain output flags") + + x_output_beg = self.get('x_output%beg') + x_output_end = self.get('x_output%end') + self.prohibit(x_output_beg is None or x_output_end is None, + "x_output%beg and x_output%end must be set for output_partial_domain") + + if n is not None and n != 0: + y_output_beg = self.get('y_output%beg') + y_output_end = self.get('y_output%end') + self.prohibit(y_output_beg is None or y_output_end is None, + "y_output%beg and y_output%end must be set for output_partial_domain with n > 0") + + if p is not None and p != 0: + z_output_beg = self.get('z_output%beg') + z_output_end = self.get('z_output%end') + self.prohibit(z_output_beg is None or z_output_end is None, + "z_output%beg and z_output%end must be set for output_partial_domain with p > 0") + + for direction in ['x', 'y', 'z']: + beg = self.get(f'{direction}_output%beg') + end = self.get(f'{direction}_output%end') + if beg is not None and end is not None: + self.prohibit(beg > end, + f"{direction}_output%beg must be <= {direction}_output%end") + + def check_partial_density(self): + """Checks partial density output constraints (post-process)""" + num_fluids = self.get('num_fluids') + model_eqns = self.get('model_eqns') + + if num_fluids is None: + return + + for i in range(1, num_fluids + 1): + alpha_rho_wrt = self.get(f'alpha_rho_wrt({i})', 'F') == 'T' + if alpha_rho_wrt: + self.prohibit(model_eqns == 1, + f"alpha_rho_wrt({i}) is not supported for model_eqns = 1") + + def check_momentum_post(self): + """Checks momentum output constraints (post-process)""" + mom_wrt = [self.get(f'mom_wrt({i})', 'F') == 'T' for i in range(1, 4)] + n = self.get('n', 0) + p = self.get('p', 0) + + self.prohibit(n == 0 and mom_wrt[1], + "mom_wrt(2) requires n > 0") + self.prohibit(p == 0 and mom_wrt[2], + "mom_wrt(3) requires p > 0") + + def check_velocity_post(self): + """Checks velocity output constraints (post-process)""" + vel_wrt = [self.get(f'vel_wrt({i})', 'F') == 'T' for i in range(1, 4)] + n = self.get('n', 0) + p = self.get('p', 0) + + self.prohibit(n == 0 and vel_wrt[1], + "vel_wrt(2) requires n > 0") + self.prohibit(p == 0 and vel_wrt[2], + "vel_wrt(3) requires p > 0") + + def check_flux_limiter(self): + """Checks flux limiter constraints (post-process)""" + flux_wrt = [self.get(f'flux_wrt({i})', 'F') == 'T' for i in range(1, 4)] + flux_lim = self.get('flux_lim') + n = self.get('n', 0) + p = self.get('p', 0) + + self.prohibit(n == 0 and flux_wrt[1], + "flux_wrt(2) requires n > 0") + self.prohibit(p == 0 and flux_wrt[2], + "flux_wrt(3) requires p > 0") + + if flux_lim is not None: + self.prohibit(flux_lim not in [1, 2, 3, 4, 5, 6, 7], + "flux_lim must be between 1 and 7") + + def check_volume_fraction(self): + """Checks volume fraction output constraints (post-process)""" + num_fluids = self.get('num_fluids') + model_eqns = self.get('model_eqns') + + if num_fluids is None: + return + + for i in range(1, num_fluids + 1): + alpha_wrt = self.get(f'alpha_wrt({i})', 'F') == 'T' + if alpha_wrt: + self.prohibit(model_eqns == 1, + f"alpha_wrt({i}) is not supported for model_eqns = 1") + + def check_fft(self): + """Checks FFT output constraints (post-process)""" + fft_wrt = self.get('fft_wrt', 'F') == 'T' + + if not fft_wrt: + return + + n = self.get('n', 0) + p = self.get('p', 0) + cyl_coord = self.get('cyl_coord', 'F') == 'T' + m_glb = self.get('m_glb') + n_glb = self.get('n_glb') + p_glb = self.get('p_glb') + + self.prohibit(n == 0 or p == 0, + "FFT WRT only supported in 3D") + self.prohibit(cyl_coord, + "FFT WRT incompatible with cylindrical coordinates") + + if m_glb is not None and n_glb is not None and p_glb is not None: + self.prohibit((m_glb + 1) % 2 != 0 or (n_glb + 1) % 2 != 0 or (p_glb + 1) % 2 != 0, + "FFT WRT requires global dimensions divisible by 2") + + # BC checks: all boundaries must be periodic (-1) + for direction in ['x', 'y', 'z']: + for end in ['beg', 'end']: + bc_val = self.get(f'bc_{direction}%{end}') + if bc_val is not None: + self.prohibit(bc_val != -1, + "FFT WRT requires periodic BCs (all BCs should be -1)") + + def check_qm(self): + """Checks Q-criterion output constraints (post-process)""" + qm_wrt = self.get('qm_wrt', 'F') == 'T' + n = self.get('n', 0) + + self.prohibit(n == 0 and qm_wrt, + "qm_wrt requires n > 0 (at least 2D)") + + def check_liutex_post(self): + """Checks liutex output constraints (post-process)""" + liutex_wrt = self.get('liutex_wrt', 'F') == 'T' + n = self.get('n', 0) + + self.prohibit(n == 0 and liutex_wrt, + "liutex_wrt requires n > 0 (at least 2D)") + + def check_surface_tension_post(self): + """Checks surface tension output constraints (post-process)""" + cf_wrt = self.get('cf_wrt', 'F') == 'T' + surface_tension = self.get('surface_tension', 'F') == 'T' + + self.prohibit(cf_wrt and not surface_tension, + "cf_wrt can only be enabled if surface_tension is enabled") + + def check_no_flow_variables(self): # pylint: disable=too-many-locals + """Checks that at least one flow variable is selected (post-process)""" + rho_wrt = self.get('rho_wrt', 'F') == 'T' + E_wrt = self.get('E_wrt', 'F') == 'T' + pres_wrt = self.get('pres_wrt', 'F') == 'T' + gamma_wrt = self.get('gamma_wrt', 'F') == 'T' + heat_ratio_wrt = self.get('heat_ratio_wrt', 'F') == 'T' + pi_inf_wrt = self.get('pi_inf_wrt', 'F') == 'T' + pres_inf_wrt = self.get('pres_inf_wrt', 'F') == 'T' + cons_vars_wrt = self.get('cons_vars_wrt', 'F') == 'T' + prim_vars_wrt = self.get('prim_vars_wrt', 'F') == 'T' + c_wrt = self.get('c_wrt', 'F') == 'T' + schlieren_wrt = self.get('schlieren_wrt', 'F') == 'T' + + # Check array variables + num_fluids = self.get('num_fluids') + if num_fluids is None: + num_fluids = 1 + alpha_rho_wrt_any = any(self.get(f'alpha_rho_wrt({i})', 'F') == 'T' for i in range(1, num_fluids + 1)) + mom_wrt_any = any(self.get(f'mom_wrt({i})', 'F') == 'T' for i in range(1, 4)) + vel_wrt_any = any(self.get(f'vel_wrt({i})', 'F') == 'T' for i in range(1, 4)) + flux_wrt_any = any(self.get(f'flux_wrt({i})', 'F') == 'T' for i in range(1, 4)) + alpha_wrt_any = any(self.get(f'alpha_wrt({i})', 'F') == 'T' for i in range(1, num_fluids + 1)) + omega_wrt_any = any(self.get(f'omega_wrt({i})', 'F') == 'T' for i in range(1, 4)) + + has_output = (rho_wrt or E_wrt or pres_wrt or gamma_wrt or heat_ratio_wrt or + pi_inf_wrt or pres_inf_wrt or cons_vars_wrt or prim_vars_wrt or + c_wrt or schlieren_wrt or alpha_rho_wrt_any or mom_wrt_any or + vel_wrt_any or flux_wrt_any or alpha_wrt_any or omega_wrt_any) + + self.prohibit(not has_output, + "None of the flow variables have been selected for post-process") + + # =================================================================== + # Main Validation Entry Points + # =================================================================== + + def validate_common(self): + """Validate parameters common to all stages""" + self.check_simulation_domain() + self.check_model_eqns_and_num_fluids() + self.check_igr() + self.check_weno() + self.check_muscl() + self.check_boundary_conditions() + self.check_bubbles_euler() + self.check_qbmm_and_polydisperse() + self.check_adv_n() + self.check_hypoelasticity() + self.check_hyperelasticity() + self.check_phase_change() + self.check_ibm() + self.check_stiffened_eos() + self.check_surface_tension() + self.check_mhd() + + def validate_simulation(self): + """Validate simulation-specific parameters""" + self.validate_common() + self.check_finite_difference() + self.check_time_stepping() + self.check_riemann_solver() + self.check_weno_simulation() + self.check_muscl_simulation() + self.check_model_eqns_simulation() + self.check_bubbles_euler_simulation() + self.check_body_forces() + self.check_viscosity() + self.check_mhd_simulation() + self.check_igr_simulation() + self.check_acoustic_source() + self.check_adaptive_time_stepping() + self.check_alt_soundspeed() + self.check_bubbles_lagrange() + self.check_continuum_damage() + self.check_grcbc() + self.check_probe_integral_output() + + def validate_pre_process(self): + """Validate pre-process-specific parameters""" + self.validate_common() + self.check_restart() + self.check_qbmm_pre_process() + self.check_parallel_io_pre_process() + self.check_grid_stretching() + self.check_perturb_density() + self.check_chemistry() + self.check_misc_pre_process() + self.check_bc_patches() + + def validate_post_process(self): + """Validate post-process-specific parameters""" + self.validate_common() + self.check_finite_difference() + self.check_time_stepping() + self.check_output_format() + self.check_partial_domain() + self.check_partial_density() + self.check_momentum_post() + self.check_velocity_post() + self.check_flux_limiter() + self.check_volume_fraction() + self.check_vorticity() + self.check_fft() + self.check_qm() + self.check_liutex_post() + self.check_schlieren() + self.check_surface_tension_post() + self.check_no_flow_variables() + + def validate(self, stage: str = 'simulation'): + """Main validation method + + Args: + stage: One of 'simulation', 'pre_process', or 'post_process' + Other stages (like 'syscheck') have no case constraints and are skipped + + Raises: + CaseConstraintError: If any constraint violations are found + """ + self.errors = [] + + if stage == 'simulation': + self.validate_simulation() + elif stage == 'pre_process': + self.validate_pre_process() + elif stage == 'post_process': + self.validate_post_process() + else: + # No stage-specific constraints for auxiliary targets like 'syscheck'. + # Silently skip validation rather than treating this as an error. + return + + if self.errors: + error_msg = "Case parameter constraint violations:\n" + "\n".join(f" • {err}" for err in self.errors) + raise CaseConstraintError(error_msg) + + +def validate_case_constraints(params: Dict[str, Any], stage: str = 'simulation'): + """Convenience function to validate case parameters + + Args: + params: Dictionary of case parameters + stage: One of 'simulation', 'pre_process', or 'post_process' + + Raises: + CaseConstraintError: If any constraint violations are found + """ + validator = CaseValidator(params) + validator.validate(stage) diff --git a/toolchain/mfc/run/input.py b/toolchain/mfc/run/input.py index c79aca11a..8a90b9c91 100644 --- a/toolchain/mfc/run/input.py +++ b/toolchain/mfc/run/input.py @@ -7,6 +7,7 @@ from .. import common, build from ..state import ARGS, ARG, gpuConfigOptions from ..case import Case +from .. import case_validator @dataclasses.dataclass(init=False) class MFCInputFile(Case): @@ -94,8 +95,20 @@ def generate_fpp(self, target) -> None: cons.unindent() + def validate_constraints(self, target) -> None: + """Validate case parameter constraints for a given target stage""" + target_obj = build.get_target(target) + stage = target_obj.name + + try: + case_validator.validate_case_constraints(self.params, stage) + except case_validator.CaseConstraintError as e: + raise common.MFCException(f"Case validation failed for {stage}:\n{e}") from e + # Generate case.fpp & [target.name].inp def generate(self, target) -> None: + # Validate constraints before generating input files + self.validate_constraints(target) self.generate_inp(target) cons.print() self.generate_fpp(target) diff --git a/toolchain/mfc/run/run.py b/toolchain/mfc/run/run.py index fb7d528ff..99d5b3d6c 100644 --- a/toolchain/mfc/run/run.py +++ b/toolchain/mfc/run/run.py @@ -117,9 +117,7 @@ def __generate_input_files(targets, case: input.MFCInputFile): for target in targets: cons.print(f"Generating input files for [magenta]{target.name}[/magenta]...") cons.indent() - cons.print() - case.generate_inp(target) - cons.print() + case.generate(target) cons.unindent() diff --git a/toolchain/mfc/sched.py b/toolchain/mfc/sched.py index ea5f0ed1f..7869468ff 100644 --- a/toolchain/mfc/sched.py +++ b/toolchain/mfc/sched.py @@ -43,7 +43,7 @@ class WorkerThreadHolder: # pylint: disable=too-many-instance-attributes thread: threading.Thread ppn: int load: float - devices: typing.Set[int] + devices: typing.Optional[typing.Set[int]] task: typing.Optional['Task'] = None start: float = 0.0 # Track which milestones we've already logged @@ -60,7 +60,7 @@ class Task: args: typing.List[typing.Any] load: float -def sched(tasks: typing.List[Task], nThreads: int, devices: typing.Set[int] = None) -> None: # pylint: disable=too-many-locals,too-many-statements +def sched(tasks: typing.List[Task], nThreads: int, devices: typing.Optional[typing.Set[int]] = None) -> None: # pylint: disable=too-many-locals,too-many-branches,too-many-statements nAvailable: int = nThreads threads: typing.List[WorkerThreadHolder] = [] @@ -197,19 +197,12 @@ def join_first_dead_thread(progress, complete_tracker, interactive: bool) -> Non f"This may indicate a system threading issue or hung test case.") from join_exc # Check for and propagate any exceptions that occurred in the worker thread - # But only if the worker function didn't complete successfully - # (This allows test failures to be handled gracefully by handle_case) if threadHolder.thread.exc is not None: - if threadHolder.thread.completed_successfully: - # Test framework handled the exception gracefully (e.g., test failure) - # Don't re-raise - this is expected behavior - pass - # Unhandled exception - this indicates a real problem - elif hasattr(threadHolder.thread, 'exc_info') and threadHolder.thread.exc_info: + # Unhandled exception - propagate with full traceback if available + if threadHolder.thread.exc_info: error_msg = f"Worker thread {threadID} failed with unhandled exception:\n{threadHolder.thread.exc_info}" raise RuntimeError(error_msg) from threadHolder.thread.exc - else: - raise threadHolder.thread.exc + raise threadHolder.thread.exc # Print completion message for long-running tests in interactive mode if interactive and threadHolder.notified_interactive: