From 26dbb1e46f8743e379eb4517f63245d7cb66dc5e Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Mon, 20 Apr 2026 16:36:49 -0700 Subject: [PATCH 01/51] Add 3D-1D coupling via svOneDSolver shared library interface --- Code/Source/solver/CMakeLists.txt | 2 + Code/Source/solver/ComMod.cpp | 11 + Code/Source/solver/ComMod.h | 27 ++ Code/Source/solver/Parameters.cpp | 31 ++ Code/Source/solver/Parameters.h | 43 ++- Code/Source/solver/baf_ini.cpp | 5 + Code/Source/solver/read_files.cpp | 11 +- Code/Source/solver/set_bc.cpp | 7 + .../sv1D_interface/OneDSolverInterface.cpp | 108 +++++++ .../sv1D_interface/OneDSolverInterface.h | 86 ++++++ Code/Source/solver/sv1D_subroutines.cpp | 282 ++++++++++++++++++ Code/Source/solver/sv1D_subroutines.h | 25 ++ Code/Source/solver/txt.cpp | 5 + 13 files changed, 641 insertions(+), 2 deletions(-) create mode 100644 Code/Source/solver/sv1D_interface/OneDSolverInterface.cpp create mode 100644 Code/Source/solver/sv1D_interface/OneDSolverInterface.h create mode 100644 Code/Source/solver/sv1D_subroutines.cpp create mode 100644 Code/Source/solver/sv1D_subroutines.h diff --git a/Code/Source/solver/CMakeLists.txt b/Code/Source/solver/CMakeLists.txt index c546c2822..14863afea 100644 --- a/Code/Source/solver/CMakeLists.txt +++ b/Code/Source/solver/CMakeLists.txt @@ -204,6 +204,7 @@ set(CSRCS stokes.h stokes.cpp sv_struct.h sv_struct.cpp svZeroD_interface.h svZeroD_interface.cpp + sv1D_subroutines.h sv1D_subroutines.cpp txt.h txt.cpp utils.h utils.cpp ustruct.h ustruct.cpp @@ -228,6 +229,7 @@ set(CSRCS SPLIT.c svZeroD_interface/LPNSolverInterface.h svZeroD_interface/LPNSolverInterface.cpp + sv1D_interface/OneDSolverInterface.h sv1D_interface/OneDSolverInterface.cpp BoundaryCondition.h BoundaryCondition.cpp RobinBoundaryCondition.h RobinBoundaryCondition.cpp diff --git a/Code/Source/solver/ComMod.cpp b/Code/Source/solver/ComMod.cpp index 0ac2d4365..a9110b67f 100644 --- a/Code/Source/solver/ComMod.cpp +++ b/Code/Source/solver/ComMod.cpp @@ -217,3 +217,14 @@ void svZeroDSolverInterfaceType::set_data(const svZeroDSolverInterfaceParameters has_data = true; } + +void sv1DSolverInterfaceType::set_data(const svOneDSolverInterfaceParameters& params) +{ + if (!params.defined()) { + return; + } + + solver_library = params.shared_library(); + input_file = params.input_file(); + has_data = true; +} diff --git a/Code/Source/solver/ComMod.h b/Code/Source/solver/ComMod.h index 8b41e5c6a..b64d10427 100644 --- a/Code/Source/solver/ComMod.h +++ b/Code/Source/solver/ComMod.h @@ -807,6 +807,28 @@ class svZeroDSolverInterfaceType void set_data(const svZeroDSolverInterfaceParameters& params); }; +//---------------------------- +// sv1DSolverInterfaceType +//---------------------------- +// This class stores information used to interface to the svOneDSolver. +// +class sv1DSolverInterfaceType +{ + public: + // Path to the 1D solver shared library (without .so/.dylib extension, + // or with extension if the full path is provided). + std::string solver_library; + + // Path to the 1D solver .in input file. + std::string input_file; + + // If the data has been set for the interface. Set to true after + // the svOneDSolver_interface XML element has been parsed. + bool has_data = false; + + void set_data(const svOneDSolverInterfaceParameters& params); +}; + /// @brief For coupled 0D-3D problems // class cplBCType @@ -822,6 +844,9 @@ class cplBCType // Whether to use svZeroD bool useSvZeroD = false; + // Whether to use sv1D (svOneDSolver) + bool useSv1D = false; + // Whether to initialize RCR from flow data bool initRCR = false; @@ -853,6 +878,8 @@ class cplBCType svZeroDSolverInterfaceType svzerod_solver_interface; + sv1DSolverInterfaceType sv1d_solver_interface; + /// @brief The name of history file containing "X" std::string saveName; //std::string(LEN=stdL) :: saveName = "LPN.dat"; diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index 38ec0fe5d..138378fc5 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -1175,6 +1175,34 @@ void svZeroDSolverInterfaceParameters::set_values(tinyxml2::XMLElement* xml_elem value_set = true; } +////////////////////////////////////////////////////////// +// svOneDSolverInterfaceParameters // +////////////////////////////////////////////////////////// + +const std::string svOneDSolverInterfaceParameters::xml_element_name_ = "svOneDSolver_interface"; + +svOneDSolverInterfaceParameters::svOneDSolverInterfaceParameters() +{ + bool required = true; + + set_parameter("Coupling_type", "", required, coupling_type); + set_parameter("Shared_library", "", required, shared_library); + set_parameter("Input_file", "", required, input_file); +} + +void svOneDSolverInterfaceParameters::set_values(tinyxml2::XMLElement* xml_elem) +{ + std::string error_msg = "Unknown svOneDSolver_interface XML element '"; + + using std::placeholders::_1; + using std::placeholders::_2; + std::function ftpr = + std::bind(&svOneDSolverInterfaceParameters::set_parameter_value, *this, _1, _2); + xml_util_set_parameters(ftpr, xml_elem, error_msg); + + value_set = true; +} + ////////////////////////////////////////////////////////// // OutputParameters // ////////////////////////////////////////////////////////// @@ -2415,6 +2443,9 @@ void EquationParameters::set_values(tinyxml2::XMLElement* eq_elem, DomainParamet } else if (name == svZeroDSolverInterfaceParameters::xml_element_name_) { svzerodsolver_interface_parameters.set_values(item); + } else if (name == svOneDSolverInterfaceParameters::xml_element_name_) { + svonedsolver_interface_parameters.set_values(item); + } else if (name == DomainParameters::xml_element_name_) { auto domain_params = new DomainParameters(); domain_params->set_values(item); diff --git a/Code/Source/solver/Parameters.h b/Code/Source/solver/Parameters.h index b726ba47f..c3a8650b3 100644 --- a/Code/Source/solver/Parameters.h +++ b/Code/Source/solver/Parameters.h @@ -679,7 +679,46 @@ class svZeroDSolverInterfaceParameters : public ParameterLists bool value_set = false; }; -/// @brief Body force over a mesh using the "Add_BF" command. +//---------------------------------- +// svOneDSolverInterfaceParameters +//---------------------------------- +/// @brief Parameters for coupling to the svOneDSolver (1D blood-flow solver). +/// +/// XML element: \code {.xml} +/// +/// Implicit +/// /path/to/libsvoned_interface +/// /path/to/solver.in +/// +/// \endcode +/// +/// Notes +/// ----- +/// Coupling_type: "Explicit" | "Implicit" | "Semi-implicit" +/// Controls how the 3D Newton iteration couples to the 1D solver +/// (same semantics as the 0D coupling_type). +/// Shared_library: Path to the 1D interface shared library. The +/// extension (.so or .dylib) may be omitted; it will be appended +/// automatically based on the platform. +/// Input_file: Path to the svOneDSolver .in input file. +// +class svOneDSolverInterfaceParameters : public ParameterLists +{ + public: + svOneDSolverInterfaceParameters(); + + static const std::string xml_element_name_; + + bool defined() const { return value_set; }; + void set_values(tinyxml2::XMLElement* xml_elem); + + Parameter coupling_type; + Parameter shared_library; + Parameter input_file; + + bool value_set = false; +}; + /// /// \code {.xml} /// @@ -1528,6 +1567,8 @@ class EquationParameters : public ParameterLists svZeroDSolverInterfaceParameters svzerodsolver_interface_parameters; + svOneDSolverInterfaceParameters svonedsolver_interface_parameters; + DomainParameters* default_domain = nullptr; std::vector domains; diff --git a/Code/Source/solver/baf_ini.cpp b/Code/Source/solver/baf_ini.cpp index 0c6201f42..4edc2658f 100644 --- a/Code/Source/solver/baf_ini.cpp +++ b/Code/Source/solver/baf_ini.cpp @@ -12,6 +12,7 @@ #include "set_bc.h" #include "utils.h" #include "svZeroD_interface.h" +#include "sv1D_subroutines.h" #include "fsils_api.hpp" #include "fils_struct.hpp" @@ -162,6 +163,10 @@ void baf_ini(Simulation* simulation, SolutionStates& solutions) svZeroD::init_svZeroD(com_mod, cm_mod); } + if (com_mod.cplBC.useSv1D) { + sv1D::init_sv1D(com_mod, cm_mod); + } + // Initialize cap integration for Coupled boundary conditions for (int iEq = 0; iEq < com_mod.nEq; iEq++) { auto& eq = com_mod.eq[iEq]; diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 65b9aaaf1..64a1fb059 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -1425,9 +1425,15 @@ void read_eq(Simulation* simulation, EquationParameters* eq_params, eqType& lEq) cplBC.useSvZeroD = true; cplbc_type_str = eq_params->svzerodsolver_interface_parameters.coupling_type.value(); cplBC.svzerod_solver_interface.set_data(eq_params->svzerodsolver_interface_parameters); + } else if (eq_params->svonedsolver_interface_parameters.defined()) { + cplBC.useSv1D = true; + cplbc_type_str = eq_params->svonedsolver_interface_parameters.coupling_type.value(); + cplBC.sv1d_solver_interface.set_data(eq_params->svonedsolver_interface_parameters); } - if (eq_params->couple_to_genBC.defined() || eq_params->svzerodsolver_interface_parameters.defined()) { + if (eq_params->couple_to_genBC.defined() || + eq_params->svzerodsolver_interface_parameters.defined() || + eq_params->svonedsolver_interface_parameters.defined()) { try { cplBC.schm = consts::cplbc_name_to_type.at(cplbc_type_str); } catch (const std::out_of_range& exception) { @@ -1446,6 +1452,9 @@ void read_eq(Simulation* simulation, EquationParameters* eq_params, eqType& lEq) } else if (cplBC.useSvZeroD) { cplBC.nX = 0; + + } else if (cplBC.useSv1D) { + cplBC.nX = 0; } } } diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index 5277f6009..67a9967a2 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -18,6 +18,7 @@ #include "utils.h" #include #include "svZeroD_interface.h" +#include "sv1D_subroutines.h" namespace set_bc { @@ -173,6 +174,8 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& set_bc::genBC_Integ_X(com_mod, cm_mod, "D"); } else if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); + } else if (cplBC.useSv1D) { + sv1D::calc_sv1D(com_mod, cm_mod, 'D'); } else { set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); } @@ -221,6 +224,8 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& set_bc::genBC_Integ_X(com_mod, cm_mod, "D"); } else if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); + } else if (cplBC.useSv1D) { + sv1D::calc_sv1D(com_mod, cm_mod, 'D'); } else { set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); } @@ -793,6 +798,8 @@ void set_bc_cpl(ComMod& com_mod, CmMod& cm_mod, const SolutionStates& solutions) set_bc::genBC_Integ_X(com_mod, cm_mod, "D"); } else if (cplBC.useSvZeroD){ svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); + } else if (cplBC.useSv1D) { + sv1D::calc_sv1D(com_mod, cm_mod, 'D'); } else { set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); } diff --git a/Code/Source/solver/sv1D_interface/OneDSolverInterface.cpp b/Code/Source/solver/sv1D_interface/OneDSolverInterface.cpp new file mode 100644 index 000000000..b211b1021 --- /dev/null +++ b/Code/Source/solver/sv1D_interface/OneDSolverInterface.cpp @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. +// SPDX-License-Identifier: BSD-3-Clause + +#include "OneDSolverInterface.h" + +#include +#include +#include + +OneDSolverInterface::~OneDSolverInterface() +{ + if (library_handle_) { + dlclose(library_handle_); + library_handle_ = nullptr; + } +} + +void OneDSolverInterface::load_library(const std::string& interface_lib) +{ + library_handle_ = dlopen(interface_lib.c_str(), RTLD_LAZY); + if (!library_handle_) { + throw std::runtime_error(std::string("[OneDSolverInterface] Could not load shared library '") + + interface_lib + "': " + dlerror()); + } + + // Clear any existing error. + dlerror(); + + auto load_sym = [&](const char* name) -> void* { + void* sym = dlsym(library_handle_, name); + const char* err = dlerror(); + if (err) { + throw std::runtime_error(std::string("[OneDSolverInterface] Could not load symbol '") + + name + "': " + err); + } + return sym; + }; + + *(void**)(&initialize_1d_) = load_sym("initialize_1d"); + *(void**)(&set_external_step_size_1d_) = load_sym("set_external_step_size_1d"); + *(void**)(&return_1d_solution_) = load_sym("return_1d_solution"); + *(void**)(&update_1d_solution_) = load_sym("update_1d_solution"); + *(void**)(&run_1d_simulation_step_1d_) = load_sym("run_1d_simulation_step_1d"); + *(void**)(&extract_coupled_dof_) = load_sym("extract_coupled_dof"); +} + +void OneDSolverInterface::initialize(const std::string& input_file, + int& problem_id, + int& system_size, + const std::string& coupling_type) +{ + if (!initialize_1d_) { + throw std::runtime_error("[OneDSolverInterface] initialize_1d not loaded"); + } + initialize_1d_(input_file.c_str(), problem_id, system_size, + coupling_type.c_str()); + problem_id_ = problem_id; + system_size_ = system_size; +} + +void OneDSolverInterface::set_external_step_size(int problem_id, double dt) +{ + if (!set_external_step_size_1d_) { + throw std::runtime_error("[OneDSolverInterface] set_external_step_size_1d not loaded"); + } + set_external_step_size_1d_(problem_id, dt); +} + +void OneDSolverInterface::return_solution(int problem_id, double* solution, int size) +{ + if (!return_1d_solution_) { + throw std::runtime_error("[OneDSolverInterface] return_1d_solution not loaded"); + } + return_1d_solution_(problem_id, solution, size); +} + +void OneDSolverInterface::update_solution(int problem_id, double* solution, int size) +{ + if (!update_1d_solution_) { + throw std::runtime_error("[OneDSolverInterface] update_1d_solution not loaded"); + } + update_1d_solution_(problem_id, solution, size); +} + +void OneDSolverInterface::run_step(int problem_id, double current_time, + int save_flag, + const std::string& coupling_type, + double* params, double* solution, + double& cpl_value, int& error_code) +{ + if (!run_1d_simulation_step_1d_) { + throw std::runtime_error("[OneDSolverInterface] run_1d_simulation_step_1d not loaded"); + } + run_1d_simulation_step_1d_(problem_id, current_time, save_flag, + coupling_type.c_str(), params, solution, + cpl_value, error_code); +} + +void OneDSolverInterface::extract_coupled_dof(int problem_id, int& coupled_dof, + const std::string& coupling_type) +{ + if (!extract_coupled_dof_) { + throw std::runtime_error("[OneDSolverInterface] extract_coupled_dof not loaded"); + } + // The shared-library signature uses a non-const char* buffer. + std::string buf = coupling_type; + extract_coupled_dof_(problem_id, coupled_dof, &buf[0]); +} diff --git a/Code/Source/solver/sv1D_interface/OneDSolverInterface.h b/Code/Source/solver/sv1D_interface/OneDSolverInterface.h new file mode 100644 index 000000000..974589d38 --- /dev/null +++ b/Code/Source/solver/sv1D_interface/OneDSolverInterface.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef ONEDSOLVER_INTERFACE_H +#define ONEDSOLVER_INTERFACE_H + +#include +#include +#include + +/// @brief Wrapper class for dynamically loading and calling the 1D solver shared library. +/// +/// This class uses dlopen/dlsym to load the libsvoned_interface shared library at runtime +/// and provides C++ method wrappers for each of its exported C functions. +/// +/// Shared library functions: +/// - initialize_1d(input_file, problem_id, system_size, coupling_type) +/// - set_external_step_size_1d(problem_id, dt) +/// - return_1d_solution(problem_id, solution, size) +/// - update_1d_solution(problem_id, solution, size) +/// - run_1d_simulation_step_1d(problem_id, time, save_flag, coupling_type, +/// params, solution, cpl_value, error_code) +/// - extract_coupled_dof(problem_id, coupled_dof, coupling_type) +// +class OneDSolverInterface { + public: + OneDSolverInterface() = default; + ~OneDSolverInterface(); + + /// @brief Load the 1D solver shared library from the given path. + void load_library(const std::string& interface_lib); + + /// @brief Initialize the 1D solver from an input file. + /// @param input_file Path to the 1D solver .in file. + /// @param problem_id Output: problem identifier assigned by the solver. + /// @param system_size Output: total number of DOFs (nodes * 2: flow + area). + /// @param coupling_type "NEU" or "DIR" coupling direction. + void initialize(const std::string& input_file, int& problem_id, + int& system_size, const std::string& coupling_type); + + /// @brief Synchronize the 1D solver's internal time step with the 3D solver. + void set_external_step_size(int problem_id, double dt); + + /// @brief Copy the current 1D solution into the caller-provided buffer. + void return_solution(int problem_id, double* solution, int size); + + /// @brief Push a solution vector into the 1D solver as the current state. + void update_solution(int problem_id, double* solution, int size); + + /// @brief Advance the 1D solver by one time step. + /// @param problem_id Problem identifier. + /// @param current_time Current simulation time (start of the step). + /// @param save_flag Non-zero to write VTK output for this step. + /// @param coupling_type "NEU" or "DIR". + /// @param params Array [N, t1, t2, ..., val1, val2, ...] where N=2. + /// @param solution In/out: solution vector updated after the step. + /// @param cpl_value Output: the BC value returned by the 1D solver + /// (pressure for NEU, flow for DIR). + /// @param error_code Output: non-zero on failure. + void run_step(int problem_id, double current_time, int save_flag, + const std::string& coupling_type, double* params, + double* solution, double& cpl_value, int& error_code); + + /// @brief Retrieve the index within the solution vector that corresponds to + /// the coupled boundary DOF. + void extract_coupled_dof(int problem_id, int& coupled_dof, + const std::string& coupling_type); + + // Public data members set after initialize(). + int problem_id_ = 0; + int system_size_ = 0; + + private: + void* library_handle_ = nullptr; + + // Function pointers to shared-library symbols. + void (*initialize_1d_)(const char*, int&, int&, const char*) = nullptr; + void (*set_external_step_size_1d_)(int, double) = nullptr; + void (*return_1d_solution_)(int, double*, int) = nullptr; + void (*update_1d_solution_)(int, double*, int) = nullptr; + void (*run_1d_simulation_step_1d_)(int, double, int, const char*, double*, + double*, double&, int&) = nullptr; + void (*extract_coupled_dof_)(int, int&, char*) = nullptr; +}; + +#endif // ONEDSOLVER_INTERFACE_H diff --git a/Code/Source/solver/sv1D_subroutines.cpp b/Code/Source/solver/sv1D_subroutines.cpp new file mode 100644 index 000000000..22dfc6f20 --- /dev/null +++ b/Code/Source/solver/sv1D_subroutines.cpp @@ -0,0 +1,282 @@ +// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. +// SPDX-License-Identifier: BSD-3-Clause + +// 3D-1D coupling subroutines. +// +// These routines interface the 3D finite-element solver (svMultiPhysics) with +// the 1D blood-flow solver (svOneDSolver) via a dynamically loaded shared +// library (libsvoned_interface.so/.dylib). +// +// Coupling overview +// ----------------- +// NEU coupling (1D inlet driven by 3D outflow): +// 3D → 1D : flow rate Q (as params[3..4]) +// 1D → 3D : pressure P (as cplBCvalue) +// BC applied on 3D face : Neumann (pressure traction) +// +// DIR coupling (1D outlet driven by 3D pressure): +// 3D → 1D : pressure P (as params[3..4]) +// 1D → 3D : flow rate Q (as cplBCvalue) +// BC applied on 3D face : Dirichlet (velocity profile) +// +// params array passed to run_1d_simulation_step_1d_: +// params[0] = 2.0 (number of time points) +// params[1] = t_old (time at start of step) +// params[2] = t_new (time at end of step) +// params[3] = BC_val_old (Q or P at t_old) +// params[4] = BC_val_new (Q or P at t_new) + +#include "sv1D_subroutines.h" + +#include +#include +#include +#include +#include +#include + +#include "ComMod.h" +#include "consts.h" +#include "utils.h" +#include "sv1D_interface/OneDSolverInterface.h" + +namespace sv1D { + +// --------------------------------------------------------------------------- +// Module-level state (one 1D solver instance per simulation). +// --------------------------------------------------------------------------- + +static OneDSolverInterface* oned_interface = nullptr; + +// Problem identifier assigned by the 1D library during initialization. +static int oned_model_id = 0; + +// Total DOF count of the 1D solution vector (nodes * 2: flow + area/pressure). +static int oned_system_size = 0; + +// "NEU" or "DIR" — determined from the face BC type at initialization time. +static std::string oned_coupling_type; + +// Index within the 1D solution vector that corresponds to the coupled DOF. +static int oned_coupled_dof = 0; + +// 1D solution vector maintained by the 3D solver. +// This is the authoritative snapshot used to restore the 1D state before +// each call to run_step(). +static std::vector oned_solution; + +// Simulation time tracked for the 1D solver (advanced only on 'L' steps). +static double sv1DTime = 0.0; + +// --------------------------------------------------------------------------- +// Helper: find the first BC that uses cplBC.fa (iBC_cpl) and sv1D. +// Returns {ptr, bGrp} or throws. +// --------------------------------------------------------------------------- +static std::pair find_1d_face(const ComMod& com_mod) +{ + using namespace consts; + const int iEq = 0; + const auto& eq = com_mod.eq[iEq]; + for (int iBc = 0; iBc < eq.nBc; iBc++) { + const auto& bc = eq.bc[iBc]; + int ptr = bc.cplBCptr; + if (ptr == -1) continue; + if (!utils::btest(bc.bType, iBC_cpl)) continue; + return {ptr, com_mod.cplBC.fa[ptr].bGrp}; + } + throw std::runtime_error("[sv1D::find_1d_face] No 1D-coupled face found in eq[0]."); +} + +// --------------------------------------------------------------------------- +// init_sv1D +// --------------------------------------------------------------------------- +void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) +{ + using namespace consts; + + auto& cplBC = com_mod.cplBC; + auto& solver_if = cplBC.sv1d_solver_interface; + auto& cm = com_mod.cm; + + if (!solver_if.has_data) { + throw std::runtime_error("[sv1D::init_sv1D] sv1D solver interface data is missing."); + } + + // Determine the coupling direction (NEU or DIR) from the BC type of the + // first coupled face. Only master needs this but we resolve it globally. + auto [ptr, bGrp] = find_1d_face(com_mod); + oned_coupling_type = (bGrp == CplBCType::cplBC_Neu) ? "NEU" : "DIR"; + + if (cm.mas(cm_mod)) { + // ----- Load shared library ----- + const std::string lib_base = solver_if.solver_library; + // Try .so first (Linux), then .dylib (macOS). + std::string lib_path; + { + std::ifstream f_so(lib_base + ".so"); + std::ifstream f_dy(lib_base + ".dylib"); + if (f_so.good()) { + lib_path = lib_base + ".so"; + } else if (f_dy.good()) { + lib_path = lib_base + ".dylib"; + } else { + // Try using the path as-is (already includes extension). + lib_path = lib_base; + } + } + + oned_interface = new OneDSolverInterface(); + oned_interface->load_library(lib_path); + + // ----- Initialize 1D solver ----- + int problem_id = 0; + int system_size = 0; + oned_interface->initialize(solver_if.input_file, problem_id, + system_size, oned_coupling_type); + oned_model_id = problem_id; + oned_system_size = system_size; + + // ----- Synchronize time step ----- + oned_interface->set_external_step_size(oned_model_id, com_mod.dt); + + // ----- Retrieve coupled DOF index ----- + oned_interface->extract_coupled_dof(oned_model_id, oned_coupled_dof, + oned_coupling_type); + + // ----- Copy initial 1D solution to local buffer ----- + oned_solution.resize(oned_system_size, 0.0); + oned_interface->return_solution(oned_model_id, oned_solution.data(), + oned_system_size); + + // ----- Set initial cplBC.fa[ptr].y ----- + // For NEU: initial BC value is the pressure at the coupled node. + // For DIR: initial BC value is the flow at the coupled node. + // The coupled DOF index is oned_coupled_dof (0-based) in the solution + // vector, where even indices = flow, odd indices = area or pressure + // depending on the 1D formulation. The shared library returns the + // correct cplBCvalue via run_step, so we initialise y = 0 here and let + // the first calc_sv1D call set the real value. + cplBC.fa[ptr].y = 0.0; + } + + // Broadcast system_size and coupling metadata to slave processes. + cm.bcast(cm_mod, &oned_system_size); + cm.bcast(cm_mod, &oned_coupled_dof); + + // Slave processes also allocate the solution buffer (unused but kept for + // any future parallel extension). + if (cm.slv(cm_mod)) { + oned_solution.resize(oned_system_size, 0.0); + } + + // Run one 'D' step so that the initial resistance term bc.r is populated + // before the first Newton iteration (matches the svZeroD pattern). + if (cplBC.schm != CplBCType::cplBC_E) { + // Flowrate/pressure are 0 at t=0; calc_sv1D handles this gracefully. + calc_sv1D(com_mod, cm_mod, 'D'); + } +} + +// --------------------------------------------------------------------------- +// calc_sv1D +// --------------------------------------------------------------------------- +void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) +{ + using namespace consts; + + auto& cplBC = com_mod.cplBC; + auto& cm = com_mod.cm; + + // Only the master process drives the 1D solver. + if (cm.mas(cm_mod)) { + const int iEq = 0; + auto& eq = com_mod.eq[iEq]; + + double t_old = sv1DTime; + double t_new = sv1DTime + com_mod.dt; + + // Save solution so that perturbation steps can be undone. + std::vector solution_snapshot = oned_solution; + + for (int iBc = 0; iBc < eq.nBc; iBc++) { + auto& bc = eq.bc[iBc]; + int ptr = bc.cplBCptr; + if (ptr == -1) continue; + if (!utils::btest(bc.bType, iBC_cpl)) continue; + + // Build params = [2, t_old, t_new, BC_val_old, BC_val_new] + double params[5]; + params[0] = 2.0; + params[1] = t_old; + params[2] = t_new; + + if (cplBC.fa[ptr].bGrp == CplBCType::cplBC_Neu) { + // NEU: 3D provides flow Q → 1D returns pressure P. + params[3] = cplBC.fa[ptr].Qo; + params[4] = cplBC.fa[ptr].Qn; + } else { + // DIR: 3D provides pressure P → 1D returns flow Q. + params[3] = cplBC.fa[ptr].Po; + params[4] = cplBC.fa[ptr].Pn; + } + + // Use a working copy of the solution so that perturbation steps + // ('D') do not corrupt the committed state in oned_solution. + std::vector work_sol = oned_solution; + + // Push the stored (committed) solution into the 1D solver. + oned_interface->update_solution(oned_model_id, work_sol.data(), + oned_system_size); + + // Save flag: write VTK output only on the final 'L' step. + int save_flag = (BCFlag == 'L') ? 1 : 0; + + double cpl_value = 0.0; + int error_code = 0; + + oned_interface->run_step(oned_model_id, t_old, save_flag, + oned_coupling_type, params, + work_sol.data(), cpl_value, error_code); + + if (error_code != 0) { + throw std::runtime_error("[sv1D::calc_sv1D] 1D solver step failed with error code " + + std::to_string(error_code)); + } + + // Store the returned BC value. + cplBC.fa[ptr].y = cpl_value; + + // On the final iteration only, commit the updated solution so that + // the next time step starts from the correct state. + if (BCFlag == 'L') { + oned_solution = work_sol; + } + } + + // Advance the 1D simulation clock on the final iteration. + if (BCFlag == 'L') { + sv1DTime += com_mod.dt; + } + } + + // Broadcast updated cplBC.fa[i].y to all parallel processes. + if (!cm.seq()) { + Vector y(cplBC.nFa); + + if (cm.mas(cm_mod)) { + for (int i = 0; i < cplBC.nFa; i++) { + y(i) = cplBC.fa[i].y; + } + } + + cm.bcast(cm_mod, y); + + if (cm.slv(cm_mod)) { + for (int i = 0; i < cplBC.nFa; i++) { + cplBC.fa[i].y = y(i); + } + } + } +} + +} // namespace sv1D diff --git a/Code/Source/solver/sv1D_subroutines.h b/Code/Source/solver/sv1D_subroutines.h new file mode 100644 index 000000000..e368dcfb5 --- /dev/null +++ b/Code/Source/solver/sv1D_subroutines.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SV1D_SUBROUTINES_H +#define SV1D_SUBROUTINES_H + +#include "Simulation.h" +#include "consts.h" +#include "sv1D_interface/OneDSolverInterface.h" + +namespace sv1D { + +/// @brief Initialize the 1D solver and populate the initial cplBC state. +/// Called once from baf_ini() after the BC data structures are set up. +void init_sv1D(ComMod& com_mod, const CmMod& cm_mod); + +/// @brief Advance the 1D solver by one time step and update the coupled BC value. +/// +/// @param BCFlag 'D' - derivative / perturbation step (state is NOT committed). +/// 'L' - last Newton iteration (state IS committed, time advances). +void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag); + +} // namespace sv1D + +#endif // SV1D_SUBROUTINES_H diff --git a/Code/Source/solver/txt.cpp b/Code/Source/solver/txt.cpp index 462cd8281..c9d56fce2 100644 --- a/Code/Source/solver/txt.cpp +++ b/Code/Source/solver/txt.cpp @@ -13,6 +13,7 @@ #include "utils.h" #include #include "svZeroD_interface.h" +#include "sv1D_subroutines.h" namespace txt_ns { @@ -176,6 +177,10 @@ void txt(Simulation* simulation, const bool init_write, const SolutionStates& so set_bc::genBC_Integ_X(com_mod, cm_mod, "L"); } else if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'L'); + + } else if (cplBC.useSv1D) { + sv1D::calc_sv1D(com_mod, cm_mod, 'L'); + } else { for (auto& bc : com_mod.eq[0].bc) { if (utils::btest(bc.bType, iBC_RCR)) { From 9512433c02f2ebed4085a8b8dec8d0f3bbf9f389 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:27 -0700 Subject: [PATCH 02/51] Fix code review issues: remove unused variable, safer char* buffer, clarify time argument --- .../Source/solver/sv1D_interface/OneDSolverInterface.cpp | 9 ++++++--- Code/Source/solver/sv1D_subroutines.cpp | 7 ++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Code/Source/solver/sv1D_interface/OneDSolverInterface.cpp b/Code/Source/solver/sv1D_interface/OneDSolverInterface.cpp index b211b1021..bae1c9ec1 100644 --- a/Code/Source/solver/sv1D_interface/OneDSolverInterface.cpp +++ b/Code/Source/solver/sv1D_interface/OneDSolverInterface.cpp @@ -6,6 +6,7 @@ #include #include #include +#include OneDSolverInterface::~OneDSolverInterface() { @@ -102,7 +103,9 @@ void OneDSolverInterface::extract_coupled_dof(int problem_id, int& coupled_dof, if (!extract_coupled_dof_) { throw std::runtime_error("[OneDSolverInterface] extract_coupled_dof not loaded"); } - // The shared-library signature uses a non-const char* buffer. - std::string buf = coupling_type; - extract_coupled_dof_(problem_id, coupled_dof, &buf[0]); + // Copy into a mutable buffer; the shared-library function signature uses + // char* (not const char*) so we must pass a writable copy. + std::vector buf(coupling_type.begin(), coupling_type.end()); + buf.push_back('\0'); + extract_coupled_dof_(problem_id, coupled_dof, buf.data()); } diff --git a/Code/Source/solver/sv1D_subroutines.cpp b/Code/Source/solver/sv1D_subroutines.cpp index 22dfc6f20..6b1864272 100644 --- a/Code/Source/solver/sv1D_subroutines.cpp +++ b/Code/Source/solver/sv1D_subroutines.cpp @@ -195,9 +195,6 @@ void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) double t_old = sv1DTime; double t_new = sv1DTime + com_mod.dt; - // Save solution so that perturbation steps can be undone. - std::vector solution_snapshot = oned_solution; - for (int iBc = 0; iBc < eq.nBc; iBc++) { auto& bc = eq.bc[iBc]; int ptr = bc.cplBCptr; @@ -205,6 +202,8 @@ void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) if (!utils::btest(bc.bType, iBC_cpl)) continue; // Build params = [2, t_old, t_new, BC_val_old, BC_val_new] + // params[1..2] carry the time window; t_old is also passed as the + // standalone current_time argument required by the 1D library API. double params[5]; params[0] = 2.0; params[1] = t_old; @@ -223,8 +222,6 @@ void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) // Use a working copy of the solution so that perturbation steps // ('D') do not corrupt the committed state in oned_solution. std::vector work_sol = oned_solution; - - // Push the stored (committed) solution into the 1D solver. oned_interface->update_solution(oned_model_id, work_sol.data(), oned_system_size); From a5f1b56aecd3531c55dfe09ba3c4743ee3fb6b50 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:28 -0700 Subject: [PATCH 03/51] Redesign 1D coupling for parallel per-model execution across MPI ranks --- Code/Source/solver/ComMod.cpp | 1 - Code/Source/solver/ComMod.h | 11 +- Code/Source/solver/Parameters.cpp | 8 +- Code/Source/solver/Parameters.h | 10 +- Code/Source/solver/baf_ini.cpp | 5 + Code/Source/solver/read_files.cpp | 25 +- Code/Source/solver/sv1D_subroutines.cpp | 313 ++++++++++++------------ 7 files changed, 201 insertions(+), 172 deletions(-) diff --git a/Code/Source/solver/ComMod.cpp b/Code/Source/solver/ComMod.cpp index a9110b67f..d9b0b676c 100644 --- a/Code/Source/solver/ComMod.cpp +++ b/Code/Source/solver/ComMod.cpp @@ -225,6 +225,5 @@ void sv1DSolverInterfaceType::set_data(const svOneDSolverInterfaceParameters& pa } solver_library = params.shared_library(); - input_file = params.input_file(); has_data = true; } diff --git a/Code/Source/solver/ComMod.h b/Code/Source/solver/ComMod.h index b64d10427..da89e70e5 100644 --- a/Code/Source/solver/ComMod.h +++ b/Code/Source/solver/ComMod.h @@ -204,6 +204,10 @@ class bcType // Coupled BC class CoupledBoundaryCondition coupled_bc; + + // sv1D: per-face 1D solver input file path (set when Time_dependence=Coupled + // and svOneDSolver_interface is active). + std::string oned_input_file; }; /// @brief Class storing data for B-Splines. @@ -771,6 +775,9 @@ class cplFaceType // RCR type BC rcrType RCR; + + // sv1D: path to the per-face 1D solver input file. + std::string oned_input_file; }; //---------------------------- @@ -819,11 +826,9 @@ class sv1DSolverInterfaceType // or with extension if the full path is provided). std::string solver_library; - // Path to the 1D solver .in input file. - std::string input_file; - // If the data has been set for the interface. Set to true after // the svOneDSolver_interface XML element has been parsed. + // Note: the per-face input files are stored in cplFaceType::oned_input_file. bool has_data = false; void set_data(const svOneDSolverInterfaceParameters& params); diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index 138378fc5..16a6e9b49 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -466,8 +466,9 @@ void BoundaryConditionRCRParameters::set_values(tinyxml2::XMLElement* xml_elem) CouplingInterfaceParameters::CouplingInterfaceParameters() { bool required = false; - set_parameter("svZeroDSolver_block", "", !required, svzerod_solver_block); - set_parameter("Chamber_cap_surface", "", !required, chamber_cap_surface); + set_parameter("svZeroDSolver_block", "", !required, svzerod_solver_block); + set_parameter("Chamber_cap_surface", "", !required, chamber_cap_surface); + set_parameter("svOneDSolver_input_file","", !required, svoned_input_file); } void CouplingInterfaceParameters::set_values(tinyxml2::XMLElement* xml_elem) @@ -1187,7 +1188,8 @@ svOneDSolverInterfaceParameters::svOneDSolverInterfaceParameters() set_parameter("Coupling_type", "", required, coupling_type); set_parameter("Shared_library", "", required, shared_library); - set_parameter("Input_file", "", required, input_file); + // Note: Input_file is no longer defined here. Each coupled face specifies + // its own 1D model input file via . } void svOneDSolverInterfaceParameters::set_values(tinyxml2::XMLElement* xml_elem) diff --git a/Code/Source/solver/Parameters.h b/Code/Source/solver/Parameters.h index c3a8650b3..0eb060477 100644 --- a/Code/Source/solver/Parameters.h +++ b/Code/Source/solver/Parameters.h @@ -688,7 +688,6 @@ class svZeroDSolverInterfaceParameters : public ParameterLists /// /// Implicit /// /path/to/libsvoned_interface -/// /path/to/solver.in /// /// \endcode /// @@ -700,7 +699,10 @@ class svZeroDSolverInterfaceParameters : public ParameterLists /// Shared_library: Path to the 1D interface shared library. The /// extension (.so or .dylib) may be omitted; it will be appended /// automatically based on the platform. -/// Input_file: Path to the svOneDSolver .in input file. +/// +/// Each coupled face specifies its own input file via +/// ... +/// inside the corresponding element. // class svOneDSolverInterfaceParameters : public ParameterLists { @@ -714,7 +716,6 @@ class svOneDSolverInterfaceParameters : public ParameterLists Parameter coupling_type; Parameter shared_library; - Parameter input_file; bool value_set = false; }; @@ -802,6 +803,9 @@ class CouplingInterfaceParameters : public ParameterLists Parameter svzerod_solver_block; Parameter chamber_cap_surface; + // Path to the svOneDSolver .in input file for this face (1D coupling). + Parameter svoned_input_file; + bool value_set = false; }; diff --git a/Code/Source/solver/baf_ini.cpp b/Code/Source/solver/baf_ini.cpp index 4edc2658f..7153a2c4f 100644 --- a/Code/Source/solver/baf_ini.cpp +++ b/Code/Source/solver/baf_ini.cpp @@ -123,6 +123,11 @@ void baf_ini(Simulation* simulation, SolutionStates& solutions) com_mod.cplBC.fa[i].name = com_mod.msh[iM].fa[iFa].name; com_mod.cplBC.fa[i].y = 0.0; + // Copy per-face 1D input file if present (sv1D coupling). + if (!bc.oned_input_file.empty()) { + com_mod.cplBC.fa[i].oned_input_file = bc.oned_input_file; + } + if (utils::btest(bc.bType, iBC_Dir)) { com_mod.cplBC.fa[i].bGrp = CplBCType::cplBC_Dir; diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 64a1fb059..1288e3ec9 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -264,10 +264,27 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, } } else { - // Coupled BC to GenBC - if (ci_set) { - throw std::runtime_error( - "[read_bc] is only valid when is defined on the equation."); + // genBC / sv1D path: uses cplBC.fa mechanism. + const bool sv1d_iface = com_mod.cplBC.sv1d_solver_interface.has_data; + + if (sv1d_iface) { + // For sv1D: each coupled face must specify its own 1D input file via + // ... + if (!ci_set || !bc_params->coupling_interface.svoned_input_file.defined()) { + throw std::runtime_error( + std::string("[read_bc] With , each 1D-coupled face needs " + " with (Time_dependence Coupled) " + "on face '") + + face_name + "'."); + } + lBc.oned_input_file = bc_params->coupling_interface.svoned_input_file.value(); + + } else { + if (ci_set) { + throw std::runtime_error( + "[read_bc] is only valid when or " + " is defined on the equation."); + } } lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_cpl)); diff --git a/Code/Source/solver/sv1D_subroutines.cpp b/Code/Source/solver/sv1D_subroutines.cpp index 6b1864272..bb3a8e101 100644 --- a/Code/Source/solver/sv1D_subroutines.cpp +++ b/Code/Source/solver/sv1D_subroutines.cpp @@ -11,14 +11,26 @@ // ----------------- // NEU coupling (1D inlet driven by 3D outflow): // 3D → 1D : flow rate Q (as params[3..4]) -// 1D → 3D : pressure P (as cplBCvalue) +// 1D → 3D : pressure P (as cpl_value) // BC applied on 3D face : Neumann (pressure traction) // // DIR coupling (1D outlet driven by 3D pressure): // 3D → 1D : pressure P (as params[3..4]) -// 1D → 3D : flow rate Q (as cplBCvalue) +// 1D → 3D : flow rate Q (as cpl_value) // BC applied on 3D face : Dirichlet (velocity profile) // +// Parallelism model +// ----------------- +// Unlike the 0D solver (which is solved once on the master rank), each +// 1D model is INDEPENDENT and has its own input file. Multiple 1D models +// are therefore solved in parallel: +// +// - Collect all sv1D-coupled faces into a list indexed 0..N-1. +// - Assign face (model) k to MPI rank k % nProcs. +// - Each rank owns and solves only its assigned model(s). +// - After solving, results are shared via MPI_Bcast from every owner +// rank so that ALL ranks know ALL cplBC.fa[i].y values. +// // params array passed to run_1d_simulation_step_1d_: // params[0] = 2.0 (number of time points) // params[1] = t_old (time at start of step) @@ -40,51 +52,64 @@ #include "utils.h" #include "sv1D_interface/OneDSolverInterface.h" +#include "mpi.h" + namespace sv1D { // --------------------------------------------------------------------------- -// Module-level state (one 1D solver instance per simulation). +// Per-model state. Each entry corresponds to one sv1D-coupled face (one 1D +// model). Indexed by the sequential order in which faces were added to +// cplBC.fa (i.e., entry k corresponds to the k-th sv1D face, which has +// cplBCptr == face_ptr_list[k]). // --------------------------------------------------------------------------- -static OneDSolverInterface* oned_interface = nullptr; +struct OneDModelState { + // Interface object (null on ranks that do not own this model). + OneDSolverInterface* interface = nullptr; + + // Problem identifier returned by the 1D library. + int problem_id = 0; + + // Total DOF count (nodes × 2). + int system_size = 0; + + // "NEU" or "DIR". + std::string coupling_type; + + // Index in the solution vector that corresponds to the coupled BC DOF. + int coupled_dof = 0; + + // Authoritative solution vector (only valid on the owning rank). + std::vector solution; -// Problem identifier assigned by the 1D library during initialization. -static int oned_model_id = 0; + // Owning MPI rank for this model. + int owner_rank = 0; -// Total DOF count of the 1D solution vector (nodes * 2: flow + area/pressure). -static int oned_system_size = 0; + // Index into cplBC.fa[] that this model services. + int fa_ptr = -1; +}; -// "NEU" or "DIR" — determined from the face BC type at initialization time. -static std::string oned_coupling_type; +// --------------------------------------------------------------------------- +// Module-level state. +// --------------------------------------------------------------------------- -// Index within the 1D solution vector that corresponds to the coupled DOF. -static int oned_coupled_dof = 0; +// One entry per sv1D-coupled face, filled during init_sv1D(). +static std::vector oned_models; -// 1D solution vector maintained by the 3D solver. -// This is the authoritative snapshot used to restore the 1D state before -// each call to run_step(). -static std::vector oned_solution; +// Shared library handle (one per process, loaded once). +static OneDSolverInterface* shared_lib_instance = nullptr; -// Simulation time tracked for the 1D solver (advanced only on 'L' steps). +// Simulation time (advanced only on 'L' steps). static double sv1DTime = 0.0; // --------------------------------------------------------------------------- -// Helper: find the first BC that uses cplBC.fa (iBC_cpl) and sv1D. -// Returns {ptr, bGrp} or throws. +// Helper: resolve the shared-library path (.so / .dylib / as-is). // --------------------------------------------------------------------------- -static std::pair find_1d_face(const ComMod& com_mod) +static std::string resolve_lib_path(const std::string& lib_base) { - using namespace consts; - const int iEq = 0; - const auto& eq = com_mod.eq[iEq]; - for (int iBc = 0; iBc < eq.nBc; iBc++) { - const auto& bc = eq.bc[iBc]; - int ptr = bc.cplBCptr; - if (ptr == -1) continue; - if (!utils::btest(bc.bType, iBC_cpl)) continue; - return {ptr, com_mod.cplBC.fa[ptr].bGrp}; - } - throw std::runtime_error("[sv1D::find_1d_face] No 1D-coupled face found in eq[0]."); + if (std::ifstream(lib_base + ".so").good()) return lib_base + ".so"; + if (std::ifstream(lib_base + ".dylib").good()) return lib_base + ".dylib"; + return lib_base; // already has extension, or will fail at dlopen time } // --------------------------------------------------------------------------- @@ -97,82 +122,79 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) auto& cplBC = com_mod.cplBC; auto& solver_if = cplBC.sv1d_solver_interface; auto& cm = com_mod.cm; + const int nProcs = cm.nProcs; + const int myRank = cm.taskId; if (!solver_if.has_data) { throw std::runtime_error("[sv1D::init_sv1D] sv1D solver interface data is missing."); } - // Determine the coupling direction (NEU or DIR) from the BC type of the - // first coupled face. Only master needs this but we resolve it globally. - auto [ptr, bGrp] = find_1d_face(com_mod); - oned_coupling_type = (bGrp == CplBCType::cplBC_Neu) ? "NEU" : "DIR"; - - if (cm.mas(cm_mod)) { - // ----- Load shared library ----- - const std::string lib_base = solver_if.solver_library; - // Try .so first (Linux), then .dylib (macOS). - std::string lib_path; - { - std::ifstream f_so(lib_base + ".so"); - std::ifstream f_dy(lib_base + ".dylib"); - if (f_so.good()) { - lib_path = lib_base + ".so"; - } else if (f_dy.good()) { - lib_path = lib_base + ".dylib"; - } else { - // Try using the path as-is (already includes extension). - lib_path = lib_base; - } + // ----- Collect the list of sv1D-coupled faces ----- + // We iterate over eq[0]'s BCs and pick those with iBC_cpl AND a non-empty + // oned_input_file. Their cplBCptr values are the indices into cplBC.fa[]. + { + const int iEq = 0; + const auto& eq = com_mod.eq[iEq]; + for (int iBc = 0; iBc < eq.nBc; iBc++) { + const auto& bc = eq.bc[iBc]; + int ptr = bc.cplBCptr; + if (ptr == -1) continue; + if (!utils::btest(bc.bType, iBC_cpl)) continue; + if (cplBC.fa[ptr].oned_input_file.empty()) continue; + + OneDModelState st; + st.fa_ptr = ptr; + st.coupling_type = (cplBC.fa[ptr].bGrp == CplBCType::cplBC_Neu) ? "NEU" : "DIR"; + oned_models.push_back(std::move(st)); } + } - oned_interface = new OneDSolverInterface(); - oned_interface->load_library(lib_path); - - // ----- Initialize 1D solver ----- - int problem_id = 0; - int system_size = 0; - oned_interface->initialize(solver_if.input_file, problem_id, - system_size, oned_coupling_type); - oned_model_id = problem_id; - oned_system_size = system_size; - - // ----- Synchronize time step ----- - oned_interface->set_external_step_size(oned_model_id, com_mod.dt); - - // ----- Retrieve coupled DOF index ----- - oned_interface->extract_coupled_dof(oned_model_id, oned_coupled_dof, - oned_coupling_type); - - // ----- Copy initial 1D solution to local buffer ----- - oned_solution.resize(oned_system_size, 0.0); - oned_interface->return_solution(oned_model_id, oned_solution.data(), - oned_system_size); - - // ----- Set initial cplBC.fa[ptr].y ----- - // For NEU: initial BC value is the pressure at the coupled node. - // For DIR: initial BC value is the flow at the coupled node. - // The coupled DOF index is oned_coupled_dof (0-based) in the solution - // vector, where even indices = flow, odd indices = area or pressure - // depending on the 1D formulation. The shared library returns the - // correct cplBCvalue via run_step, so we initialise y = 0 here and let - // the first calc_sv1D call set the real value. - cplBC.fa[ptr].y = 0.0; + if (oned_models.empty()) { + throw std::runtime_error("[sv1D::init_sv1D] No sv1D-coupled faces with input files found."); } - // Broadcast system_size and coupling metadata to slave processes. - cm.bcast(cm_mod, &oned_system_size); - cm.bcast(cm_mod, &oned_coupled_dof); + // ----- Load shared library (once per process) ----- + const std::string lib_path = resolve_lib_path(solver_if.solver_library); + shared_lib_instance = new OneDSolverInterface(); + shared_lib_instance->load_library(lib_path); + + // ----- Assign ranks and initialize owned models ----- + const int nModels = static_cast(oned_models.size()); + for (int k = 0; k < nModels; k++) { + auto& st = oned_models[k]; + st.owner_rank = k % nProcs; + + if (myRank == st.owner_rank) { + // This rank owns model k: initialize it. + const std::string& input_file = cplBC.fa[st.fa_ptr].oned_input_file; + int problem_id = 0; + int system_size = 0; + + shared_lib_instance->initialize(input_file, problem_id, system_size, + st.coupling_type); + st.problem_id = problem_id; + st.system_size = system_size; + st.interface = shared_lib_instance; + + shared_lib_instance->set_external_step_size(problem_id, com_mod.dt); + shared_lib_instance->extract_coupled_dof(problem_id, st.coupled_dof, + st.coupling_type); + + st.solution.resize(system_size, 0.0); + shared_lib_instance->return_solution(problem_id, st.solution.data(), system_size); + + // Initial cplBC.fa y = 0; first calc_sv1D call sets the real value. + cplBC.fa[st.fa_ptr].y = 0.0; + } - // Slave processes also allocate the solution buffer (unused but kept for - // any future parallel extension). - if (cm.slv(cm_mod)) { - oned_solution.resize(oned_system_size, 0.0); + // Broadcast system_size and coupled_dof from owner to all ranks so that + // every rank knows the metadata (needed for consistent broadcast later). + MPI_Bcast(&st.system_size, 1, MPI_INT, st.owner_rank, cm.com()); + MPI_Bcast(&st.coupled_dof, 1, MPI_INT, st.owner_rank, cm.com()); } - // Run one 'D' step so that the initial resistance term bc.r is populated - // before the first Newton iteration (matches the svZeroD pattern). + // Run one 'D' step to populate the initial resistance term bc.r. if (cplBC.schm != CplBCType::cplBC_E) { - // Flowrate/pressure are 0 at t=0; calc_sv1D handles this gracefully. calc_sv1D(com_mod, cm_mod, 'D'); } } @@ -184,95 +206,70 @@ void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) { using namespace consts; - auto& cplBC = com_mod.cplBC; - auto& cm = com_mod.cm; + auto& cplBC = com_mod.cplBC; + auto& cm = com_mod.cm; + const int myRank = cm.taskId; - // Only the master process drives the 1D solver. - if (cm.mas(cm_mod)) { - const int iEq = 0; - auto& eq = com_mod.eq[iEq]; + const double t_old = sv1DTime; + const double t_new = sv1DTime + com_mod.dt; + const int nModels = static_cast(oned_models.size()); - double t_old = sv1DTime; - double t_new = sv1DTime + com_mod.dt; + // Each rank solves its own models and then broadcasts the result. + for (int k = 0; k < nModels; k++) { + auto& st = oned_models[k]; + int fa_ptr = st.fa_ptr; - for (int iBc = 0; iBc < eq.nBc; iBc++) { - auto& bc = eq.bc[iBc]; - int ptr = bc.cplBCptr; - if (ptr == -1) continue; - if (!utils::btest(bc.bType, iBC_cpl)) continue; + double cpl_value = 0.0; + if (myRank == st.owner_rank) { // Build params = [2, t_old, t_new, BC_val_old, BC_val_new] - // params[1..2] carry the time window; t_old is also passed as the - // standalone current_time argument required by the 1D library API. double params[5]; params[0] = 2.0; params[1] = t_old; params[2] = t_new; - if (cplBC.fa[ptr].bGrp == CplBCType::cplBC_Neu) { - // NEU: 3D provides flow Q → 1D returns pressure P. - params[3] = cplBC.fa[ptr].Qo; - params[4] = cplBC.fa[ptr].Qn; + if (cplBC.fa[fa_ptr].bGrp == CplBCType::cplBC_Neu) { + params[3] = cplBC.fa[fa_ptr].Qo; + params[4] = cplBC.fa[fa_ptr].Qn; } else { - // DIR: 3D provides pressure P → 1D returns flow Q. - params[3] = cplBC.fa[ptr].Po; - params[4] = cplBC.fa[ptr].Pn; + params[3] = cplBC.fa[fa_ptr].Po; + params[4] = cplBC.fa[fa_ptr].Pn; } - // Use a working copy of the solution so that perturbation steps - // ('D') do not corrupt the committed state in oned_solution. - std::vector work_sol = oned_solution; - oned_interface->update_solution(oned_model_id, work_sol.data(), - oned_system_size); - - // Save flag: write VTK output only on the final 'L' step. - int save_flag = (BCFlag == 'L') ? 1 : 0; + // Working copy of solution so that 'D' steps don't corrupt the + // committed state. + std::vector work_sol = st.solution; + st.interface->update_solution(st.problem_id, work_sol.data(), st.system_size); - double cpl_value = 0.0; - int error_code = 0; + int save_flag = (BCFlag == 'L') ? 1 : 0; + int error_code = 0; - oned_interface->run_step(oned_model_id, t_old, save_flag, - oned_coupling_type, params, - work_sol.data(), cpl_value, error_code); + st.interface->run_step(st.problem_id, t_old, save_flag, + st.coupling_type, params, + work_sol.data(), cpl_value, error_code); if (error_code != 0) { - throw std::runtime_error("[sv1D::calc_sv1D] 1D solver step failed with error code " + - std::to_string(error_code)); + throw std::runtime_error( + "[sv1D::calc_sv1D] 1D solver step for face '" + + cplBC.fa[fa_ptr].name + "' failed with error code " + + std::to_string(error_code)); } - // Store the returned BC value. - cplBC.fa[ptr].y = cpl_value; - - // On the final iteration only, commit the updated solution so that - // the next time step starts from the correct state. + // Commit the updated solution only on the final iteration. if (BCFlag == 'L') { - oned_solution = work_sol; + st.solution = work_sol; } } - // Advance the 1D simulation clock on the final iteration. - if (BCFlag == 'L') { - sv1DTime += com_mod.dt; - } + // Broadcast the BC value returned by this model from its owner to all + // ranks so that every rank can update cplBC.fa[fa_ptr].y. + MPI_Bcast(&cpl_value, 1, MPI_DOUBLE, st.owner_rank, cm.com()); + cplBC.fa[fa_ptr].y = cpl_value; } - // Broadcast updated cplBC.fa[i].y to all parallel processes. - if (!cm.seq()) { - Vector y(cplBC.nFa); - - if (cm.mas(cm_mod)) { - for (int i = 0; i < cplBC.nFa; i++) { - y(i) = cplBC.fa[i].y; - } - } - - cm.bcast(cm_mod, y); - - if (cm.slv(cm_mod)) { - for (int i = 0; i < cplBC.nFa; i++) { - cplBC.fa[i].y = y(i); - } - } + // Advance the simulation clock after the final iteration. + if (BCFlag == 'L') { + sv1DTime += com_mod.dt; } } From 2afd2231022c681ff7e779b49fbeb4285d4f75ed Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:29 -0700 Subject: [PATCH 04/51] =?UTF-8?q?Address=20code=20review:=20init=20sv1DTim?= =?UTF-8?q?e=20from=20com=5Fmod.time,=20rename=20nModels=E2=86=92nTotalMod?= =?UTF-8?q?els,=20improve=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Code/Source/solver/Parameters.cpp | 6 ++++-- Code/Source/solver/sv1D_subroutines.cpp | 12 ++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index 16a6e9b49..1e8e6e317 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -1188,8 +1188,10 @@ svOneDSolverInterfaceParameters::svOneDSolverInterfaceParameters() set_parameter("Coupling_type", "", required, coupling_type); set_parameter("Shared_library", "", required, shared_library); - // Note: Input_file is no longer defined here. Each coupled face specifies - // its own 1D model input file via . + // Unlike svZeroDSolver (which uses a single global Input_file for all faces), + // svOneDSolver requires each coupled face to supply its own input file. + // Per-face input files are specified via + // inside each element. } void svOneDSolverInterfaceParameters::set_values(tinyxml2::XMLElement* xml_elem) diff --git a/Code/Source/solver/sv1D_subroutines.cpp b/Code/Source/solver/sv1D_subroutines.cpp index bb3a8e101..f7c8b6831 100644 --- a/Code/Source/solver/sv1D_subroutines.cpp +++ b/Code/Source/solver/sv1D_subroutines.cpp @@ -129,6 +129,10 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) throw std::runtime_error("[sv1D::init_sv1D] sv1D solver interface data is missing."); } + // Initialize the 1D simulation clock from the 3D solver's current time so + // that restarts and non-zero start times are handled correctly. + sv1DTime = com_mod.time; + // ----- Collect the list of sv1D-coupled faces ----- // We iterate over eq[0]'s BCs and pick those with iBC_cpl AND a non-empty // oned_input_file. Their cplBCptr values are the indices into cplBC.fa[]. @@ -159,8 +163,8 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) shared_lib_instance->load_library(lib_path); // ----- Assign ranks and initialize owned models ----- - const int nModels = static_cast(oned_models.size()); - for (int k = 0; k < nModels; k++) { + const int nTotalModels = static_cast(oned_models.size()); + for (int k = 0; k < nTotalModels; k++) { auto& st = oned_models[k]; st.owner_rank = k % nProcs; @@ -212,10 +216,10 @@ void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) const double t_old = sv1DTime; const double t_new = sv1DTime + com_mod.dt; - const int nModels = static_cast(oned_models.size()); + const int nTotalModels = static_cast(oned_models.size()); // Each rank solves its own models and then broadcasts the result. - for (int k = 0; k < nModels; k++) { + for (int k = 0; k < nTotalModels; k++) { auto& st = oned_models[k]; int fa_ptr = st.fa_ptr; From 68561d0c452b24f58478d76470dbe4b34ef16a91 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:29 -0700 Subject: [PATCH 05/51] Fix calc_sv1D: split solve and broadcast into two phases for true MPI parallelism --- Code/Source/solver/sv1D_subroutines.cpp | 96 +++++++++++++------------ 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/Code/Source/solver/sv1D_subroutines.cpp b/Code/Source/solver/sv1D_subroutines.cpp index f7c8b6831..2a26889e1 100644 --- a/Code/Source/solver/sv1D_subroutines.cpp +++ b/Code/Source/solver/sv1D_subroutines.cpp @@ -218,57 +218,65 @@ void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) const double t_new = sv1DTime + com_mod.dt; const int nTotalModels = static_cast(oned_models.size()); - // Each rank solves its own models and then broadcasts the result. + // ----- Phase 1: each rank runs its own models without blocking ----- + // All ranks proceed through this loop simultaneously, each executing only + // the models it owns. No MPI call here, so model k on rank A and model k+1 + // on rank B truly run at the same time. + std::vector cpl_values(nTotalModels, 0.0); + for (int k = 0; k < nTotalModels; k++) { auto& st = oned_models[k]; int fa_ptr = st.fa_ptr; - double cpl_value = 0.0; + if (myRank != st.owner_rank) continue; - if (myRank == st.owner_rank) { - // Build params = [2, t_old, t_new, BC_val_old, BC_val_new] - double params[5]; - params[0] = 2.0; - params[1] = t_old; - params[2] = t_new; - - if (cplBC.fa[fa_ptr].bGrp == CplBCType::cplBC_Neu) { - params[3] = cplBC.fa[fa_ptr].Qo; - params[4] = cplBC.fa[fa_ptr].Qn; - } else { - params[3] = cplBC.fa[fa_ptr].Po; - params[4] = cplBC.fa[fa_ptr].Pn; - } - - // Working copy of solution so that 'D' steps don't corrupt the - // committed state. - std::vector work_sol = st.solution; - st.interface->update_solution(st.problem_id, work_sol.data(), st.system_size); - - int save_flag = (BCFlag == 'L') ? 1 : 0; - int error_code = 0; - - st.interface->run_step(st.problem_id, t_old, save_flag, - st.coupling_type, params, - work_sol.data(), cpl_value, error_code); - - if (error_code != 0) { - throw std::runtime_error( - "[sv1D::calc_sv1D] 1D solver step for face '" + - cplBC.fa[fa_ptr].name + "' failed with error code " + - std::to_string(error_code)); - } - - // Commit the updated solution only on the final iteration. - if (BCFlag == 'L') { - st.solution = work_sol; - } + // Build params = [2, t_old, t_new, BC_val_old, BC_val_new] + double params[5]; + params[0] = 2.0; + params[1] = t_old; + params[2] = t_new; + + if (cplBC.fa[fa_ptr].bGrp == CplBCType::cplBC_Neu) { + params[3] = cplBC.fa[fa_ptr].Qo; + params[4] = cplBC.fa[fa_ptr].Qn; + } else { + params[3] = cplBC.fa[fa_ptr].Po; + params[4] = cplBC.fa[fa_ptr].Pn; } - // Broadcast the BC value returned by this model from its owner to all - // ranks so that every rank can update cplBC.fa[fa_ptr].y. - MPI_Bcast(&cpl_value, 1, MPI_DOUBLE, st.owner_rank, cm.com()); - cplBC.fa[fa_ptr].y = cpl_value; + // Working copy of solution so that 'D' steps don't corrupt the + // committed state. + std::vector work_sol = st.solution; + st.interface->update_solution(st.problem_id, work_sol.data(), st.system_size); + + int save_flag = (BCFlag == 'L') ? 1 : 0; + int error_code = 0; + + st.interface->run_step(st.problem_id, t_old, save_flag, + st.coupling_type, params, + work_sol.data(), cpl_values[k], error_code); + + if (error_code != 0) { + throw std::runtime_error( + "[sv1D::calc_sv1D] 1D solver step for face '" + + cplBC.fa[fa_ptr].name + "' failed with error code " + + std::to_string(error_code)); + } + + // Commit the updated solution only on the final iteration. + if (BCFlag == 'L') { + st.solution = work_sol; + } + } + + // ----- Phase 2: broadcast all results and update cplBC.fa[].y ----- + // After every rank has finished solving its own models, gather the results. + // Each MPI_Bcast here is a cheap scalar transfer; the expensive 1D solver + // work has already been done concurrently in Phase 1. + for (int k = 0; k < nTotalModels; k++) { + auto& st = oned_models[k]; + MPI_Bcast(&cpl_values[k], 1, MPI_DOUBLE, st.owner_rank, cm.com()); + cplBC.fa[st.fa_ptr].y = cpl_values[k]; } // Advance the simulation clock after the final iteration. From abe0fa6178cea9af1dd6b013a1d89f01f8794f9b Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:30 -0700 Subject: [PATCH 06/51] Support mixed sv1D + RCR boundary conditions --- Code/Source/solver/ComMod.h | 3 ++ Code/Source/solver/baf_ini.cpp | 3 ++ Code/Source/solver/read_files.cpp | 13 ++++-- Code/Source/solver/set_bc.cpp | 67 ++++++++++++++++++++++--------- 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/Code/Source/solver/ComMod.h b/Code/Source/solver/ComMod.h index da89e70e5..50d3d2f66 100644 --- a/Code/Source/solver/ComMod.h +++ b/Code/Source/solver/ComMod.h @@ -778,6 +778,9 @@ class cplFaceType // sv1D: path to the per-face 1D solver input file. std::string oned_input_file; + + // Whether this face uses RCR (Windkessel) boundary condition. + bool isRCR = false; }; //---------------------------- diff --git a/Code/Source/solver/baf_ini.cpp b/Code/Source/solver/baf_ini.cpp index 7153a2c4f..d241adfe8 100644 --- a/Code/Source/solver/baf_ini.cpp +++ b/Code/Source/solver/baf_ini.cpp @@ -145,6 +145,9 @@ void baf_ini(Simulation* simulation, SolutionStates& solutions) com_mod.cplBC.fa[i].RCR.Rd = bc.RCR.Rd; com_mod.cplBC.fa[i].RCR.Pd = bc.RCR.Pd; com_mod.cplBC.fa[i].RCR.Xo = bc.RCR.Xo; + if (utils::btest(bc.bType, iBC_RCR)) { + com_mod.cplBC.fa[i].isRCR = true; + } } else { throw std::runtime_error("Not a compatible cplBC_type"); } diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 1288e3ec9..d11f4937b 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -335,7 +335,8 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, lBc.RCR.Pd = bc_params->rcr.distal_pressure.value(); lBc.RCR.Xo = bc_params->rcr.initial_pressure.value(); - if (com_mod.cplBC.schm != CplBCType::cplBC_NA || com_mod.cplBC.xo.size() != 0) { + if ((com_mod.cplBC.schm != CplBCType::cplBC_NA && !com_mod.cplBC.useSv1D) || + com_mod.cplBC.xo.size() != 0) { throw std::runtime_error("[read_bc] RCR cannot be used in conjunction with cplBC."); } com_mod.cplBC.nFa = com_mod.cplBC.nFa + 1; @@ -1559,9 +1560,13 @@ void read_eq(Simulation* simulation, EquationParameters* eq_params, eqType& lEq) if (std::set{Equation_fluid,Equation_FSI,Equation_CMM}.count(lEq.phys) == 0) { throw std::runtime_error("RCR-type BC is allowed for fluid/CMM/FSI eq. only."); } - cplBC.schm = CplBCType::cplBC_SI; - if (lEq.useTLS) { - cplBC.schm = CplBCType::cplBC_E; + // Only set coupling scheme if not already configured by an external solver (e.g. sv1D). + // When sv1D and RCR coexist, sv1D owns the scheme; RCR uses the same scheme. + if (!cplBC.useSv1D) { + cplBC.schm = CplBCType::cplBC_SI; + if (lEq.useTLS) { + cplBC.schm = CplBCType::cplBC_E; + } } cplBC.nX = cplBC.nFa; cplBC.nXp = cplBC.nFa + 1; diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index 67a9967a2..bf9a3242b 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -176,6 +176,10 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); } else if (cplBC.useSv1D) { sv1D::calc_sv1D(com_mod, cm_mod, 'D'); + // Also integrate any RCR faces that coexist with sv1D faces. + if (RCRflag) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } } else { set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); } @@ -226,6 +230,10 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); } else if (cplBC.useSv1D) { sv1D::calc_sv1D(com_mod, cm_mod, 'D'); + // Also integrate any RCR faces that coexist with sv1D faces. + if (RCRflag) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } } else { set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); } @@ -481,26 +489,40 @@ void RCR_Integ_X(ComMod& com_mod, const CmMod& cm_mod, int istat) double tt = fmax(time - dt, 0.0); double dtt = dt / static_cast(nTS); - int nX = cplBC.nFa; + + // Collect indices of RCR faces only. When sv1D and RCR are mixed, some + // cplBC.fa[] entries belong to the 1D solver and must be skipped here to + // avoid accessing uninitialised RCR parameters (Rp/C/Rd/Pd = 0) and + // causing division-by-zero inside the RK4 loop. + std::vector rcrIdx; + rcrIdx.reserve(cplBC.nFa); + for (int i = 0; i < cplBC.nFa; i++) { + if (cplBC.fa[i].isRCR) { + rcrIdx.push_back(i); + } + } + int nX = static_cast(rcrIdx.size()); Vector Rp(nX), C(nX), Rd(nX), Pd(nX); Vector X(nX), Xrk(nX); Array frk(nX,4), Qrk(nX,4); - for (int i = 0; i < nX; i++) { - Rp(i) = cplBC.fa[i].RCR.Rp; - C(i) = cplBC.fa[i].RCR.C; - Rd(i) = cplBC.fa[i].RCR.Rd; - Pd(i) = cplBC.fa[i].RCR.Pd; + for (int k = 0; k < nX; k++) { + int i = rcrIdx[k]; + Rp(k) = cplBC.fa[i].RCR.Rp; + C(k) = cplBC.fa[i].RCR.C; + Rd(k) = cplBC.fa[i].RCR.Rd; + Pd(k) = cplBC.fa[i].RCR.Pd; + X(k) = cplBC.xo[i]; } - X = cplBC.xo; for (int n = 0; n < nTS; n++) { for (int i = 0; i < 4; i++) { double r = static_cast(i) / 3.0; r = (static_cast(n) + r) / static_cast(nTS); - for (int j = 0; j < Qrk.nrows(); j++) { - Qrk(j,i) = cplBC.fa[j].Qo + (cplBC.fa[j].Qn - cplBC.fa[j].Qo) * r; + for (int j = 0; j < nX; j++) { + int fi = rcrIdx[j]; + Qrk(j,i) = cplBC.fa[fi].Qo + (cplBC.fa[fi].Qn - cplBC.fa[fi].Qo) * r; } } @@ -517,21 +539,21 @@ void RCR_Integ_X(ComMod& com_mod, const CmMod& cm_mod, int istat) trk = tt + dtt / 3.0; Xrk = X + dtt * frk.col(0) / 3.0; - for (int j = 0; j < Qrk.nrows(); j++) { + for (int j = 0; j < nX; j++) { frk(j,1) = (Qrk(j,1) - (Xrk(j)-Pd(j)) / Rd(j)) / C(j); } // RK-4 3rd pass trk = tt + 2.0 * dtt / 3.0; Xrk = X - dtt * frk.col(0) / 3.0 + dtt * frk.col(1); - for (int j = 0; j < Qrk.nrows(); j++) { + for (int j = 0; j < nX; j++) { frk(j,2) = (Qrk(j,2) - (Xrk(j) - Pd(j)) / Rd(j)) / C(j); } // RK-4 4th pass trk = tt + dtt; Xrk = X + dtt * frk.col(0) - dtt * frk.col(1) + dtt * frk.col(2); - for (int j = 0; j < Qrk.nrows(); j++) { + for (int j = 0; j < nX; j++) { frk(j,3) = (Qrk(j,3) - (Xrk(j) - Pd(j)) / Rd(j)) / C(j); } @@ -539,8 +561,8 @@ void RCR_Integ_X(ComMod& com_mod, const CmMod& cm_mod, int istat) X = X + r*(frk.col(0) + 3.0*(frk.col(1) + frk.col(2)) + frk.col(3)); tt = tt + dtt; - for (int i = 0; i < nX; i++) { - if (isnan(X(i))) { + for (int k = 0; k < nX; k++) { + if (isnan(X(k))) { throw std::runtime_error("ERROR: NaN detected in RCR integration"); istat = -1; return; @@ -548,13 +570,14 @@ void RCR_Integ_X(ComMod& com_mod, const CmMod& cm_mod, int istat) } } - cplBC.xn = X; - cplBC.xp(0) = tt; - - for (int i = 0; i < nX; i++) { - cplBC.xp(i+1) = Qrk(i,3); //cplBC.fa(i).Qn - cplBC.fa[i].y = X(i) + (cplBC.fa[i].Qn * Rp(i)); + // Write results back using the original (sparse) face indices. + for (int k = 0; k < nX; k++) { + int i = rcrIdx[k]; + cplBC.xn[i] = X(k); + cplBC.xp(i+1) = Qrk(k,3); + cplBC.fa[i].y = X(k) + (cplBC.fa[i].Qn * Rp(k)); } + cplBC.xp(0) = tt; } @@ -800,6 +823,10 @@ void set_bc_cpl(ComMod& com_mod, CmMod& cm_mod, const SolutionStates& solutions) svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); } else if (cplBC.useSv1D) { sv1D::calc_sv1D(com_mod, cm_mod, 'D'); + // Also integrate any RCR faces that coexist with sv1D faces. + if (RCRflag) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } } else { set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); } From 4e778d54a945f4099189931cea2e4a80c6304ffd Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:31 -0700 Subject: [PATCH 07/51] Update error message to reflect RCR+sv1D is now allowed --- Code/Source/solver/read_files.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index d11f4937b..a5b71f347 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -337,7 +337,7 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, if ((com_mod.cplBC.schm != CplBCType::cplBC_NA && !com_mod.cplBC.useSv1D) || com_mod.cplBC.xo.size() != 0) { - throw std::runtime_error("[read_bc] RCR cannot be used in conjunction with cplBC."); + throw std::runtime_error("[read_bc] RCR cannot be used in conjunction with cplBC (except alongside sv1D)."); } com_mod.cplBC.nFa = com_mod.cplBC.nFa + 1; lBc.cplBCptr = com_mod.cplBC.nFa - 1; From 21daef5cc51902fd18d905fa134ab831292e5f1f Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:31 -0700 Subject: [PATCH 08/51] init_sv1D: separate init loop from metadata broadcast for true parallel initialization --- Code/Source/solver/sv1D_subroutines.cpp | 77 ++++++++++++++++--------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/Code/Source/solver/sv1D_subroutines.cpp b/Code/Source/solver/sv1D_subroutines.cpp index 2a26889e1..2eff9f38c 100644 --- a/Code/Source/solver/sv1D_subroutines.cpp +++ b/Code/Source/solver/sv1D_subroutines.cpp @@ -23,13 +23,27 @@ // ----------------- // Unlike the 0D solver (which is solved once on the master rank), each // 1D model is INDEPENDENT and has its own input file. Multiple 1D models -// are therefore solved in parallel: +// are therefore read, initialized, and solved in parallel: // -// - Collect all sv1D-coupled faces into a list indexed 0..N-1. -// - Assign face (model) k to MPI rank k % nProcs. -// - Each rank owns and solves only its assigned model(s). -// - After solving, results are shared via MPI_Bcast from every owner -// rank so that ALL ranks know ALL cplBC.fa[i].y values. +// Initialization (init_sv1D): +// Phase 1 – parallel init: +// - Collect all sv1D-coupled faces into a list indexed 0..N-1. +// - Assign face (model) k to MPI rank k % nProcs. +// - Each rank reads and initializes ONLY its owned model(s) with no +// MPI synchronization, so all ranks work simultaneously. +// Phase 2 – batch metadata exchange: +// - After every rank has finished initializing its own model(s), +// share system_size and coupled_dof via MPI_Bcast so that all +// ranks know the sizes needed for subsequent result broadcasts. +// +// Time-stepping (calc_sv1D): +// Phase 1 – parallel solve: +// - Each rank runs run_step() for its owned model(s) with no MPI +// calls, so model k on rank A and model k+1 on rank B truly run +// concurrently. +// Phase 2 – batch result exchange: +// - After every rank has finished solving, results are shared via +// MPI_Bcast so that ALL ranks know ALL cplBC.fa[i].y values. // // params array passed to run_1d_simulation_step_1d_: // params[0] = 2.0 (number of time points) @@ -162,37 +176,46 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) shared_lib_instance = new OneDSolverInterface(); shared_lib_instance->load_library(lib_path); - // ----- Assign ranks and initialize owned models ----- + // ----- Assign ranks and initialize owned models (Phase 1: parallel) ----- + // No MPI calls in this loop. All ranks proceed simultaneously, each + // reading and initializing only the model(s) it owns. Rank k owns model k + // (assigned via k % nProcs), so for N models and N ranks every rank handles + // exactly one model with no inter-rank synchronization. const int nTotalModels = static_cast(oned_models.size()); for (int k = 0; k < nTotalModels; k++) { auto& st = oned_models[k]; st.owner_rank = k % nProcs; - if (myRank == st.owner_rank) { - // This rank owns model k: initialize it. - const std::string& input_file = cplBC.fa[st.fa_ptr].oned_input_file; - int problem_id = 0; - int system_size = 0; + if (myRank != st.owner_rank) continue; - shared_lib_instance->initialize(input_file, problem_id, system_size, - st.coupling_type); - st.problem_id = problem_id; - st.system_size = system_size; - st.interface = shared_lib_instance; + // This rank owns model k: read the input file and initialize. + const std::string& input_file = cplBC.fa[st.fa_ptr].oned_input_file; + int problem_id = 0; + int system_size = 0; - shared_lib_instance->set_external_step_size(problem_id, com_mod.dt); - shared_lib_instance->extract_coupled_dof(problem_id, st.coupled_dof, - st.coupling_type); + shared_lib_instance->initialize(input_file, problem_id, system_size, + st.coupling_type); + st.problem_id = problem_id; + st.system_size = system_size; + st.interface = shared_lib_instance; - st.solution.resize(system_size, 0.0); - shared_lib_instance->return_solution(problem_id, st.solution.data(), system_size); + shared_lib_instance->set_external_step_size(problem_id, com_mod.dt); + shared_lib_instance->extract_coupled_dof(problem_id, st.coupled_dof, + st.coupling_type); - // Initial cplBC.fa y = 0; first calc_sv1D call sets the real value. - cplBC.fa[st.fa_ptr].y = 0.0; - } + st.solution.resize(system_size, 0.0); + shared_lib_instance->return_solution(problem_id, st.solution.data(), system_size); - // Broadcast system_size and coupled_dof from owner to all ranks so that - // every rank knows the metadata (needed for consistent broadcast later). + // Initial cplBC.fa y = 0; first calc_sv1D call sets the real value. + cplBC.fa[st.fa_ptr].y = 0.0; + } + + // ----- Broadcast metadata for all models (Phase 2: batch exchange) ----- + // All initialization is complete. Now share system_size and coupled_dof + // from each owner so that every rank knows the sizes needed for consistent + // result broadcasts in calc_sv1D. + for (int k = 0; k < nTotalModels; k++) { + auto& st = oned_models[k]; MPI_Bcast(&st.system_size, 1, MPI_INT, st.owner_rank, cm.com()); MPI_Bcast(&st.coupled_dof, 1, MPI_INT, st.owner_rank, cm.com()); } From 05044bda24975ded0f2a7f36259448f39f8dd08c Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:32 -0700 Subject: [PATCH 09/51] sv1D: add nProcs >= nTotalModels guard in init_sv1D --- Code/Source/solver/sv1D_subroutines.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Code/Source/solver/sv1D_subroutines.cpp b/Code/Source/solver/sv1D_subroutines.cpp index 2eff9f38c..f6b69461f 100644 --- a/Code/Source/solver/sv1D_subroutines.cpp +++ b/Code/Source/solver/sv1D_subroutines.cpp @@ -171,6 +171,20 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) throw std::runtime_error("[sv1D::init_sv1D] No sv1D-coupled faces with input files found."); } + // ----- Guard: require at least one MPI rank per 1D model ----- + // Each rank owns exactly one model (owner_rank = k % nProcs). If nProcs < N + // a single rank would own multiple models and call shared_lib_instance->initialize() + // more than once, corrupting the static problem-ID state inside the shared library. + const int nTotalModels = static_cast(oned_models.size()); + if (nProcs < nTotalModels) { + throw std::runtime_error( + "[sv1D::init_sv1D] Number of MPI processes (" + std::to_string(nProcs) + + ") is less than the number of sv1D-coupled faces (" + + std::to_string(nTotalModels) + + "). Please run with at least " + std::to_string(nTotalModels) + + " MPI processes."); + } + // ----- Load shared library (once per process) ----- const std::string lib_path = resolve_lib_path(solver_if.solver_library); shared_lib_instance = new OneDSolverInterface(); @@ -181,7 +195,6 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) // reading and initializing only the model(s) it owns. Rank k owns model k // (assigned via k % nProcs), so for N models and N ranks every rank handles // exactly one model with no inter-rank synchronization. - const int nTotalModels = static_cast(oned_models.size()); for (int k = 0; k < nTotalModels; k++) { auto& st = oned_models[k]; st.owner_rank = k % nProcs; From d088f23d353864d49c701cf0e3116751cb0ee1a2 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:33 -0700 Subject: [PATCH 10/51] =?UTF-8?q?rename=20sv1D=20=E2=86=92=20svOneD:=20fil?= =?UTF-8?q?es,=20namespace,=20functions,=20class,=20variables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Code/Source/solver/CMakeLists.txt | 4 +- Code/Source/solver/ComMod.cpp | 2 +- Code/Source/solver/ComMod.h | 12 ++-- Code/Source/solver/baf_ini.cpp | 6 +- Code/Source/solver/read_files.cpp | 10 ++-- Code/Source/solver/set_bc.cpp | 16 +++--- .../OneDSolverInterface.cpp | 0 .../OneDSolverInterface.h | 0 ...subroutines.cpp => svOneD_subroutines.cpp} | 56 +++++++++---------- ...v1D_subroutines.h => svOneD_subroutines.h} | 10 ++-- Code/Source/solver/txt.cpp | 4 +- 11 files changed, 60 insertions(+), 60 deletions(-) rename Code/Source/solver/{sv1D_interface => svOneD_interface}/OneDSolverInterface.cpp (100%) rename Code/Source/solver/{sv1D_interface => svOneD_interface}/OneDSolverInterface.h (100%) rename Code/Source/solver/{sv1D_subroutines.cpp => svOneD_subroutines.cpp} (88%) rename Code/Source/solver/{sv1D_subroutines.h => svOneD_subroutines.h} (76%) diff --git a/Code/Source/solver/CMakeLists.txt b/Code/Source/solver/CMakeLists.txt index 14863afea..05a3f1e9c 100644 --- a/Code/Source/solver/CMakeLists.txt +++ b/Code/Source/solver/CMakeLists.txt @@ -204,7 +204,7 @@ set(CSRCS stokes.h stokes.cpp sv_struct.h sv_struct.cpp svZeroD_interface.h svZeroD_interface.cpp - sv1D_subroutines.h sv1D_subroutines.cpp + svOneD_subroutines.h svOneD_subroutines.cpp txt.h txt.cpp utils.h utils.cpp ustruct.h ustruct.cpp @@ -229,7 +229,7 @@ set(CSRCS SPLIT.c svZeroD_interface/LPNSolverInterface.h svZeroD_interface/LPNSolverInterface.cpp - sv1D_interface/OneDSolverInterface.h sv1D_interface/OneDSolverInterface.cpp + svOneD_interface/OneDSolverInterface.h svOneD_interface/OneDSolverInterface.cpp BoundaryCondition.h BoundaryCondition.cpp RobinBoundaryCondition.h RobinBoundaryCondition.cpp diff --git a/Code/Source/solver/ComMod.cpp b/Code/Source/solver/ComMod.cpp index d9b0b676c..7d543dd15 100644 --- a/Code/Source/solver/ComMod.cpp +++ b/Code/Source/solver/ComMod.cpp @@ -218,7 +218,7 @@ void svZeroDSolverInterfaceType::set_data(const svZeroDSolverInterfaceParameters has_data = true; } -void sv1DSolverInterfaceType::set_data(const svOneDSolverInterfaceParameters& params) +void svOneDSolverInterfaceType::set_data(const svOneDSolverInterfaceParameters& params) { if (!params.defined()) { return; diff --git a/Code/Source/solver/ComMod.h b/Code/Source/solver/ComMod.h index 50d3d2f66..32a18b3af 100644 --- a/Code/Source/solver/ComMod.h +++ b/Code/Source/solver/ComMod.h @@ -205,7 +205,7 @@ class bcType // Coupled BC class CoupledBoundaryCondition coupled_bc; - // sv1D: per-face 1D solver input file path (set when Time_dependence=Coupled + // svOneD: per-face 1D solver input file path (set when Time_dependence=Coupled // and svOneDSolver_interface is active). std::string oned_input_file; }; @@ -776,7 +776,7 @@ class cplFaceType // RCR type BC rcrType RCR; - // sv1D: path to the per-face 1D solver input file. + // svOneD: path to the per-face 1D solver input file. std::string oned_input_file; // Whether this face uses RCR (Windkessel) boundary condition. @@ -818,11 +818,11 @@ class svZeroDSolverInterfaceType }; //---------------------------- -// sv1DSolverInterfaceType +// svOneDSolverInterfaceType //---------------------------- // This class stores information used to interface to the svOneDSolver. // -class sv1DSolverInterfaceType +class svOneDSolverInterfaceType { public: // Path to the 1D solver shared library (without .so/.dylib extension, @@ -852,7 +852,7 @@ class cplBCType // Whether to use svZeroD bool useSvZeroD = false; - // Whether to use sv1D (svOneDSolver) + // Whether to use svOneD (svOneDSolver) bool useSv1D = false; // Whether to initialize RCR from flow data @@ -886,7 +886,7 @@ class cplBCType svZeroDSolverInterfaceType svzerod_solver_interface; - sv1DSolverInterfaceType sv1d_solver_interface; + svOneDSolverInterfaceType sv1d_solver_interface; /// @brief The name of history file containing "X" std::string saveName; diff --git a/Code/Source/solver/baf_ini.cpp b/Code/Source/solver/baf_ini.cpp index d241adfe8..4f18f177f 100644 --- a/Code/Source/solver/baf_ini.cpp +++ b/Code/Source/solver/baf_ini.cpp @@ -12,7 +12,7 @@ #include "set_bc.h" #include "utils.h" #include "svZeroD_interface.h" -#include "sv1D_subroutines.h" +#include "svOneD_subroutines.h" #include "fsils_api.hpp" #include "fils_struct.hpp" @@ -123,7 +123,7 @@ void baf_ini(Simulation* simulation, SolutionStates& solutions) com_mod.cplBC.fa[i].name = com_mod.msh[iM].fa[iFa].name; com_mod.cplBC.fa[i].y = 0.0; - // Copy per-face 1D input file if present (sv1D coupling). + // Copy per-face 1D input file if present (svOneD coupling). if (!bc.oned_input_file.empty()) { com_mod.cplBC.fa[i].oned_input_file = bc.oned_input_file; } @@ -172,7 +172,7 @@ void baf_ini(Simulation* simulation, SolutionStates& solutions) } if (com_mod.cplBC.useSv1D) { - sv1D::init_sv1D(com_mod, cm_mod); + svOneD::init_svOneD(com_mod, cm_mod); } // Initialize cap integration for Coupled boundary conditions diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index a5b71f347..4461d0849 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -264,11 +264,11 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, } } else { - // genBC / sv1D path: uses cplBC.fa mechanism. + // genBC / cplBC / svOneD path: uses cplBC.fa mechanism. const bool sv1d_iface = com_mod.cplBC.sv1d_solver_interface.has_data; if (sv1d_iface) { - // For sv1D: each coupled face must specify its own 1D input file via + // For svOneD: each coupled face must specify its own 1D input file via // ... if (!ci_set || !bc_params->coupling_interface.svoned_input_file.defined()) { throw std::runtime_error( @@ -337,7 +337,7 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, if ((com_mod.cplBC.schm != CplBCType::cplBC_NA && !com_mod.cplBC.useSv1D) || com_mod.cplBC.xo.size() != 0) { - throw std::runtime_error("[read_bc] RCR cannot be used in conjunction with cplBC (except alongside sv1D)."); + throw std::runtime_error("[read_bc] RCR cannot be used in conjunction with cplBC (except alongside svOneD)."); } com_mod.cplBC.nFa = com_mod.cplBC.nFa + 1; lBc.cplBCptr = com_mod.cplBC.nFa - 1; @@ -1560,8 +1560,8 @@ void read_eq(Simulation* simulation, EquationParameters* eq_params, eqType& lEq) if (std::set{Equation_fluid,Equation_FSI,Equation_CMM}.count(lEq.phys) == 0) { throw std::runtime_error("RCR-type BC is allowed for fluid/CMM/FSI eq. only."); } - // Only set coupling scheme if not already configured by an external solver (e.g. sv1D). - // When sv1D and RCR coexist, sv1D owns the scheme; RCR uses the same scheme. + // Only set coupling scheme if not already configured by an external solver (e.g. svOneD). + // When svOneD and RCR coexist, svOneD owns the scheme; RCR uses the same scheme. if (!cplBC.useSv1D) { cplBC.schm = CplBCType::cplBC_SI; if (lEq.useTLS) { diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index bf9a3242b..a0006f8d6 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -18,7 +18,7 @@ #include "utils.h" #include #include "svZeroD_interface.h" -#include "sv1D_subroutines.h" +#include "svOneD_subroutines.h" namespace set_bc { @@ -175,8 +175,8 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& } else if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); } else if (cplBC.useSv1D) { - sv1D::calc_sv1D(com_mod, cm_mod, 'D'); - // Also integrate any RCR faces that coexist with sv1D faces. + svOneD::calc_svOneD(com_mod, cm_mod, 'D'); + // Also integrate any RCR faces that coexist with svOneD faces. if (RCRflag) { set_bc::cplBC_Integ_X(com_mod, cm_mod, true); } @@ -229,8 +229,8 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& } else if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); } else if (cplBC.useSv1D) { - sv1D::calc_sv1D(com_mod, cm_mod, 'D'); - // Also integrate any RCR faces that coexist with sv1D faces. + svOneD::calc_svOneD(com_mod, cm_mod, 'D'); + // Also integrate any RCR faces that coexist with svOneD faces. if (RCRflag) { set_bc::cplBC_Integ_X(com_mod, cm_mod, true); } @@ -490,7 +490,7 @@ void RCR_Integ_X(ComMod& com_mod, const CmMod& cm_mod, int istat) double tt = fmax(time - dt, 0.0); double dtt = dt / static_cast(nTS); - // Collect indices of RCR faces only. When sv1D and RCR are mixed, some + // Collect indices of RCR faces only. When svOneD and RCR are mixed, some // cplBC.fa[] entries belong to the 1D solver and must be skipped here to // avoid accessing uninitialised RCR parameters (Rp/C/Rd/Pd = 0) and // causing division-by-zero inside the RK4 loop. @@ -822,8 +822,8 @@ void set_bc_cpl(ComMod& com_mod, CmMod& cm_mod, const SolutionStates& solutions) } else if (cplBC.useSvZeroD){ svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); } else if (cplBC.useSv1D) { - sv1D::calc_sv1D(com_mod, cm_mod, 'D'); - // Also integrate any RCR faces that coexist with sv1D faces. + svOneD::calc_svOneD(com_mod, cm_mod, 'D'); + // Also integrate any RCR faces that coexist with svOneD faces. if (RCRflag) { set_bc::cplBC_Integ_X(com_mod, cm_mod, true); } diff --git a/Code/Source/solver/sv1D_interface/OneDSolverInterface.cpp b/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp similarity index 100% rename from Code/Source/solver/sv1D_interface/OneDSolverInterface.cpp rename to Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp diff --git a/Code/Source/solver/sv1D_interface/OneDSolverInterface.h b/Code/Source/solver/svOneD_interface/OneDSolverInterface.h similarity index 100% rename from Code/Source/solver/sv1D_interface/OneDSolverInterface.h rename to Code/Source/solver/svOneD_interface/OneDSolverInterface.h diff --git a/Code/Source/solver/sv1D_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp similarity index 88% rename from Code/Source/solver/sv1D_subroutines.cpp rename to Code/Source/solver/svOneD_subroutines.cpp index f6b69461f..3d9b8f85e 100644 --- a/Code/Source/solver/sv1D_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -25,9 +25,9 @@ // 1D model is INDEPENDENT and has its own input file. Multiple 1D models // are therefore read, initialized, and solved in parallel: // -// Initialization (init_sv1D): +// Initialization (init_svOneD): // Phase 1 – parallel init: -// - Collect all sv1D-coupled faces into a list indexed 0..N-1. +// - Collect all svOneD-coupled faces into a list indexed 0..N-1. // - Assign face (model) k to MPI rank k % nProcs. // - Each rank reads and initializes ONLY its owned model(s) with no // MPI synchronization, so all ranks work simultaneously. @@ -36,7 +36,7 @@ // share system_size and coupled_dof via MPI_Bcast so that all // ranks know the sizes needed for subsequent result broadcasts. // -// Time-stepping (calc_sv1D): +// Time-stepping (calc_svOneD): // Phase 1 – parallel solve: // - Each rank runs run_step() for its owned model(s) with no MPI // calls, so model k on rank A and model k+1 on rank B truly run @@ -52,7 +52,7 @@ // params[3] = BC_val_old (Q or P at t_old) // params[4] = BC_val_new (Q or P at t_new) -#include "sv1D_subroutines.h" +#include "svOneD_subroutines.h" #include #include @@ -64,16 +64,16 @@ #include "ComMod.h" #include "consts.h" #include "utils.h" -#include "sv1D_interface/OneDSolverInterface.h" +#include "svOneD_interface/OneDSolverInterface.h" #include "mpi.h" -namespace sv1D { +namespace svOneD { // --------------------------------------------------------------------------- -// Per-model state. Each entry corresponds to one sv1D-coupled face (one 1D +// Per-model state. Each entry corresponds to one svOneD-coupled face (one 1D // model). Indexed by the sequential order in which faces were added to -// cplBC.fa (i.e., entry k corresponds to the k-th sv1D face, which has +// cplBC.fa (i.e., entry k corresponds to the k-th svOneD face, which has // cplBCptr == face_ptr_list[k]). // --------------------------------------------------------------------------- @@ -107,14 +107,14 @@ struct OneDModelState { // Module-level state. // --------------------------------------------------------------------------- -// One entry per sv1D-coupled face, filled during init_sv1D(). +// One entry per svOneD-coupled face, filled during init_svOneD(). static std::vector oned_models; // Shared library handle (one per process, loaded once). static OneDSolverInterface* shared_lib_instance = nullptr; // Simulation time (advanced only on 'L' steps). -static double sv1DTime = 0.0; +static double svOneDTime = 0.0; // --------------------------------------------------------------------------- // Helper: resolve the shared-library path (.so / .dylib / as-is). @@ -127,9 +127,9 @@ static std::string resolve_lib_path(const std::string& lib_base) } // --------------------------------------------------------------------------- -// init_sv1D +// init_svOneD // --------------------------------------------------------------------------- -void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) +void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) { using namespace consts; @@ -140,14 +140,14 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) const int myRank = cm.taskId; if (!solver_if.has_data) { - throw std::runtime_error("[sv1D::init_sv1D] sv1D solver interface data is missing."); + throw std::runtime_error("[svOneD::init_svOneD] svOneD solver interface data is missing."); } // Initialize the 1D simulation clock from the 3D solver's current time so // that restarts and non-zero start times are handled correctly. - sv1DTime = com_mod.time; + svOneDTime = com_mod.time; - // ----- Collect the list of sv1D-coupled faces ----- + // ----- Collect the list of svOneD-coupled faces ----- // We iterate over eq[0]'s BCs and pick those with iBC_cpl AND a non-empty // oned_input_file. Their cplBCptr values are the indices into cplBC.fa[]. { @@ -168,7 +168,7 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) } if (oned_models.empty()) { - throw std::runtime_error("[sv1D::init_sv1D] No sv1D-coupled faces with input files found."); + throw std::runtime_error("[svOneD::init_svOneD] No svOneD-coupled faces with input files found."); } // ----- Guard: require at least one MPI rank per 1D model ----- @@ -178,8 +178,8 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) const int nTotalModels = static_cast(oned_models.size()); if (nProcs < nTotalModels) { throw std::runtime_error( - "[sv1D::init_sv1D] Number of MPI processes (" + std::to_string(nProcs) + - ") is less than the number of sv1D-coupled faces (" + + "[svOneD::init_svOneD] Number of MPI processes (" + std::to_string(nProcs) + + ") is less than the number of svOneD-coupled faces (" + std::to_string(nTotalModels) + "). Please run with at least " + std::to_string(nTotalModels) + " MPI processes."); @@ -219,14 +219,14 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) st.solution.resize(system_size, 0.0); shared_lib_instance->return_solution(problem_id, st.solution.data(), system_size); - // Initial cplBC.fa y = 0; first calc_sv1D call sets the real value. + // Initial cplBC.fa y = 0; first calc_svOneD call sets the real value. cplBC.fa[st.fa_ptr].y = 0.0; } // ----- Broadcast metadata for all models (Phase 2: batch exchange) ----- // All initialization is complete. Now share system_size and coupled_dof // from each owner so that every rank knows the sizes needed for consistent - // result broadcasts in calc_sv1D. + // result broadcasts in calc_svOneD. for (int k = 0; k < nTotalModels; k++) { auto& st = oned_models[k]; MPI_Bcast(&st.system_size, 1, MPI_INT, st.owner_rank, cm.com()); @@ -235,14 +235,14 @@ void init_sv1D(ComMod& com_mod, const CmMod& cm_mod) // Run one 'D' step to populate the initial resistance term bc.r. if (cplBC.schm != CplBCType::cplBC_E) { - calc_sv1D(com_mod, cm_mod, 'D'); + calc_svOneD(com_mod, cm_mod, 'D'); } } // --------------------------------------------------------------------------- -// calc_sv1D +// calc_svOneD // --------------------------------------------------------------------------- -void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) +void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) { using namespace consts; @@ -250,8 +250,8 @@ void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) auto& cm = com_mod.cm; const int myRank = cm.taskId; - const double t_old = sv1DTime; - const double t_new = sv1DTime + com_mod.dt; + const double t_old = svOneDTime; + const double t_new = svOneDTime + com_mod.dt; const int nTotalModels = static_cast(oned_models.size()); // ----- Phase 1: each rank runs its own models without blocking ----- @@ -294,7 +294,7 @@ void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) if (error_code != 0) { throw std::runtime_error( - "[sv1D::calc_sv1D] 1D solver step for face '" + + "[svOneD::calc_svOneD] 1D solver step for face '" + cplBC.fa[fa_ptr].name + "' failed with error code " + std::to_string(error_code)); } @@ -317,8 +317,8 @@ void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) // Advance the simulation clock after the final iteration. if (BCFlag == 'L') { - sv1DTime += com_mod.dt; + svOneDTime += com_mod.dt; } } -} // namespace sv1D +} // namespace svOneD diff --git a/Code/Source/solver/sv1D_subroutines.h b/Code/Source/solver/svOneD_subroutines.h similarity index 76% rename from Code/Source/solver/sv1D_subroutines.h rename to Code/Source/solver/svOneD_subroutines.h index e368dcfb5..6b8009cd0 100644 --- a/Code/Source/solver/sv1D_subroutines.h +++ b/Code/Source/solver/svOneD_subroutines.h @@ -6,20 +6,20 @@ #include "Simulation.h" #include "consts.h" -#include "sv1D_interface/OneDSolverInterface.h" +#include "svOneD_interface/OneDSolverInterface.h" -namespace sv1D { +namespace svOneD { /// @brief Initialize the 1D solver and populate the initial cplBC state. /// Called once from baf_ini() after the BC data structures are set up. -void init_sv1D(ComMod& com_mod, const CmMod& cm_mod); +void init_svOneD(ComMod& com_mod, const CmMod& cm_mod); /// @brief Advance the 1D solver by one time step and update the coupled BC value. /// /// @param BCFlag 'D' - derivative / perturbation step (state is NOT committed). /// 'L' - last Newton iteration (state IS committed, time advances). -void calc_sv1D(ComMod& com_mod, const CmMod& cm_mod, char BCFlag); +void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag); -} // namespace sv1D +} // namespace svOneD #endif // SV1D_SUBROUTINES_H diff --git a/Code/Source/solver/txt.cpp b/Code/Source/solver/txt.cpp index c9d56fce2..9d02be893 100644 --- a/Code/Source/solver/txt.cpp +++ b/Code/Source/solver/txt.cpp @@ -13,7 +13,7 @@ #include "utils.h" #include #include "svZeroD_interface.h" -#include "sv1D_subroutines.h" +#include "svOneD_subroutines.h" namespace txt_ns { @@ -179,7 +179,7 @@ void txt(Simulation* simulation, const bool init_write, const SolutionStates& so svZeroD::calc_svZeroD(com_mod, cm_mod, 'L'); } else if (cplBC.useSv1D) { - sv1D::calc_sv1D(com_mod, cm_mod, 'L'); + svOneD::calc_svOneD(com_mod, cm_mod, 'L'); } else { for (auto& bc : com_mod.eq[0].bc) { From 1127a717c2d895c2bdb88440877d73e4c28133c0 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:34 -0700 Subject: [PATCH 11/51] Add example solver.xml with 2 svOneD-coupled faces --- .../cases/fluid/pipe_svOneD_2faces/solver.xml | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 tests/cases/fluid/pipe_svOneD_2faces/solver.xml diff --git a/tests/cases/fluid/pipe_svOneD_2faces/solver.xml b/tests/cases/fluid/pipe_svOneD_2faces/solver.xml new file mode 100644 index 000000000..bc15d6bad --- /dev/null +++ b/tests/cases/fluid/pipe_svOneD_2faces/solver.xml @@ -0,0 +1,142 @@ + + + + + + + false + 3 + 100 + 0.001 + 0.50 + STOP_SIM + + 1 + result + 10 + 1 + + 100 + 0 + + 1 + 0 + 0 + + + + + + mesh-complete/mesh-complete.mesh.vtu + + + mesh-complete/mesh-surfaces/lumen_inlet.vtp + + + + mesh-complete/mesh-surfaces/lumen_outlet1.vtp + + + + mesh-complete/mesh-surfaces/lumen_outlet2.vtp + + + + mesh-complete/mesh-surfaces/lumen_wall.vtp + + + + + + 1 + 3 + 10 + 1e-3 + 0.2 + + 1.06 + + 0.04 + + + + true + true + true + true + + + + + fsils + + 10 + 3 + 500 + 1e-3 + 1e-3 + 1e-3 + 50 + + + + + semi-implicit + /path/to/svOneDSolver/build/lib/libsvOneDSolver_interface.so + + + + + Dir + Unsteady + lumen_inlet.flw + true + true + + + + + Neu + Coupled + + 1dmodel1.in + + + + + + Neu + Coupled + + 1dmodel2.in + + + + + + Dir + Steady + 0.0 + + + + + From d88edb12d206399c0a20c4dd1e5b50112e7bed16 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:34 -0700 Subject: [PATCH 12/51] Migrate svOneD 1D coupling to CoupledBoundaryCondition interface --- .../solver/CoupledBoundaryCondition.cpp | 20 +- Code/Source/solver/CoupledBoundaryCondition.h | 9 + Code/Source/solver/read_files.cpp | 174 ++++++++++++------ Code/Source/solver/set_bc.cpp | 28 ++- Code/Source/solver/svOneD_subroutines.cpp | 58 +++--- 5 files changed, 192 insertions(+), 97 deletions(-) diff --git a/Code/Source/solver/CoupledBoundaryCondition.cpp b/Code/Source/solver/CoupledBoundaryCondition.cpp index bf4ee9061..7c4f035b8 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.cpp +++ b/Code/Source/solver/CoupledBoundaryCondition.cpp @@ -22,6 +22,7 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(const CoupledBoundaryConditio , bc_type_(other.bc_type_) , block_name_(other.block_name_) , face_name_(other.face_name_) + , oned_input_file_(other.oned_input_file_) , Qo_(other.Qo_) , Qn_(other.Qn_) , Po_(other.Po_) @@ -53,7 +54,7 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(const CoupledBound bc_type_ = other.bc_type_; block_name_ = other.block_name_; face_name_ = other.face_name_; - Qo_ = other.Qo_; + oned_input_file_ = other.oned_input_file_; Qn_ = other.Qn_; Po_ = other.Po_; Pn_ = other.Pn_; @@ -83,7 +84,7 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(CoupledBoundaryCondition&& ot , bc_type_(other.bc_type_) , block_name_(std::move(other.block_name_)) , face_name_(std::move(other.face_name_)) - , Qo_(other.Qo_) + , oned_input_file_(std::move(other.oned_input_file_)) , Qn_(other.Qn_) , Po_(other.Po_) , Pn_(other.Pn_) @@ -129,7 +130,7 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(CoupledBoundaryCon bc_type_ = other.bc_type_; block_name_ = std::move(other.block_name_); face_name_ = std::move(other.face_name_); - Qo_ = other.Qo_; + oned_input_file_ = std::move(other.oned_input_file_); Qn_ = other.Qn_; Po_ = other.Po_; Pn_ = other.Pn_; @@ -208,6 +209,16 @@ const std::string& CoupledBoundaryCondition::get_block_name() const return block_name_; } +const std::string& CoupledBoundaryCondition::get_oned_input_file() const +{ + return oned_input_file_; +} + +void CoupledBoundaryCondition::set_oned_input_file(const std::string& path) +{ + oned_input_file_ = path; +} + void CoupledBoundaryCondition::set_solution_ids(int flow_id, int pressure_id, double in_out_sign) { flow_sol_id_ = flow_id; @@ -401,6 +412,9 @@ void CoupledBoundaryCondition::distribute(const ComMod& com_mod, const CmMod& cm // Distribute block name cm.bcast(cm_mod, block_name_); + // Distribute 1D input file path + cm.bcast(cm_mod, oned_input_file_); + // Distribute face name cm.bcast(cm_mod, face_name_); diff --git a/Code/Source/solver/CoupledBoundaryCondition.h b/Code/Source/solver/CoupledBoundaryCondition.h index ae6d54a6d..4e376a41a 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.h +++ b/Code/Source/solver/CoupledBoundaryCondition.h @@ -191,6 +191,8 @@ class CoupledBoundaryCondition { /// @brief svZeroD coupling data std::string block_name_; ///< Block name in svZeroDSolver configuration std::string face_name_; ///< Face name from the mesh + /// @brief svOneD coupling data + std::string oned_input_file_; ///< Path to svOneDSolver input file (empty for svZeroD BCs) /// @brief Flowrate data double Qo_ = 0.0; ///< Flowrate at old timestep (t_n) @@ -289,6 +291,13 @@ class CoupledBoundaryCondition { /// @return Block name const std::string& get_block_name() const; + /// @brief Get the svOneD input file path + /// @return Path to the 1D solver input file (empty for svZeroD BCs) + const std::string& get_oned_input_file() const; + + /// @brief Set the svOneD input file path + void set_oned_input_file(const std::string& path); + /// @brief Set the svZeroD solution IDs for flow and pressure /// @param flow_id Flow solution ID /// @param pressure_id Pressure solution ID diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 4461d0849..8e30ef82d 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -264,10 +264,10 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, } } else { - // genBC / cplBC / svOneD path: uses cplBC.fa mechanism. + // genBC / cplBC / svOneD path. const bool sv1d_iface = com_mod.cplBC.sv1d_solver_interface.has_data; - if (sv1d_iface) { + if (sv1d_iface) { // For svOneD: each coupled face must specify its own 1D input file via // ... if (!ci_set || !bc_params->coupling_interface.svoned_input_file.defined()) { @@ -279,23 +279,39 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, } lBc.oned_input_file = bc_params->coupling_interface.svoned_input_file.value(); + // svOneD now uses the CoupledBoundaryCondition interface (same flags as svZeroD). + lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_Coupled)); + lBc.bType = utils::ibclr(lBc.bType, enum_int(BoundaryConditionType::bType_Dir)); + lBc.bType = utils::ibclr(lBc.bType, enum_int(BoundaryConditionType::bType_Neu)); + lBc.bType = utils::ibclr(lBc.bType, enum_int(BoundaryConditionType::bType_bfs)); + lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_cpl)); + // cplBCptr stays -1; coupled_bc is constructed in the block below. + + if (com_mod.cplBC.schm == CplBCType::cplBC_NA) { + throw std::runtime_error( + std::string("[read_bc] A coupling method (e.g. svOneDSolver_interface coupling_type) " + "must be defined for Time_dependence Coupled on face '") + + face_name + "'."); + } + } else { + // genBC / cplBC: uses cplBC.fa array mechanism. if (ci_set) { throw std::runtime_error( "[read_bc] is only valid when or " " is defined on the equation."); } - } - lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_cpl)); - com_mod.cplBC.nFa = com_mod.cplBC.nFa + 1; - lBc.cplBCptr = com_mod.cplBC.nFa - 1; + lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_cpl)); + com_mod.cplBC.nFa = com_mod.cplBC.nFa + 1; + lBc.cplBCptr = com_mod.cplBC.nFa - 1; - if (com_mod.cplBC.schm == CplBCType::cplBC_NA) { - throw std::runtime_error( - std::string("[read_bc] A coupling method (e.g. Couple_to_genBC) must be defined for Time_dependence " - "Coupled on face '") + - face_name + "'."); + if (com_mod.cplBC.schm == CplBCType::cplBC_NA) { + throw std::runtime_error( + std::string("[read_bc] A coupling method (e.g. Couple_to_genBC) must be defined for Time_dependence " + "Coupled on face '") + + face_name + "'."); + } } } @@ -412,67 +428,105 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, } - // Coupled BC (svZeroDSolver / CoupledBoundaryCondition) + // Coupled BC (svZeroDSolver or svOneDSolver via CoupledBoundaryCondition) if (utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Coupled))) { - // Get asssociated face name and block name + // Get associated face name const auto& face_name = com_mod.msh[lBc.iM].fa[lBc.iFa].name; - const std::string zd_block = bc_params->coupling_interface.svzerod_solver_block.value(); - - // Get follower pressure load flag if defined - bool cpl_flwP = false; - if (lEq.phys == Equation_struct || lEq.phys == Equation_ustruct) { - cpl_flwP = bc_params->follower_pressure_load.value(); - } - // Sanity check: CoupledBoundaryCondition is only supported for struct, ustruct, fluid, FSI, or CMM physics - const auto cpl_phys = lEq.phys; - if (cpl_phys != Equation_struct && cpl_phys != Equation_ustruct && cpl_phys != Equation_fluid && - cpl_phys != Equation_FSI && cpl_phys != Equation_CMM) { + // Sanity check: CoupledBoundaryCondition must be Dirichlet or Neumann + if (coupled_bc_type != BoundaryConditionType::bType_Dir && + coupled_bc_type != BoundaryConditionType::bType_Neu) { throw std::runtime_error( - std::string("[read_bc] CoupledBoundaryCondition (svZeroDSolver) is only supported for struct, ustruct, fluid, FSI, or CMM physics on face '") + - face_name + "'."); - } - - // Sanity check: svZeroDSolver coupling is currently implemented only for Neumann-type boundaries. - if (!utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Neu))) { - throw std::runtime_error( - std::string("[read_bc] CoupledBoundaryCondition (svZeroDSolver) currently requires boundary Neu on face '") + + std::string("[read_bc] CoupledBoundaryCondition requires boundary Dirichlet or Neumann on face '") + face_name + "'."); } - // Sanity check: Follower pressure load must be used for 0D coupling with struct/ustruct - if ((cpl_phys == Equation_struct || cpl_phys == Equation_ustruct) && !cpl_flwP) { - throw std::runtime_error( - std::string("[read_bc] Follower pressure load must be used for 0D coupling with struct/ustruct on face '") + - face_name + "'."); - } - - // Get cap face VTP file name if defined - std::string zerod_cap; - bool use_cap = false; - if (bc_params->coupling_interface.chamber_cap_surface.defined()) { - zerod_cap = bc_params->coupling_interface.chamber_cap_surface.value(); - use_cap = true; - } + if (com_mod.cplBC.sv1d_solver_interface.has_data) { + // ------------------------------------------------------------------ + // svOneD path: construct CoupledBoundaryCondition with empty + // block_name and store the 1D input file path. + // ------------------------------------------------------------------ - // Figure out the coupled BC type - BoundaryConditionType coupled_bc_type = BoundaryConditionType::bType_Neu; - if (utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Dir))) { - coupled_bc_type = BoundaryConditionType::bType_Dir; - } else if (utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Neu))) { - coupled_bc_type = BoundaryConditionType::bType_Neu; - } + // Sanity check: only fluid, FSI, or CMM physics supported + const auto cpl_phys = lEq.phys; + if (cpl_phys != Equation_fluid && cpl_phys != Equation_FSI && cpl_phys != Equation_CMM) { + throw std::runtime_error( + std::string("[read_bc] CoupledBoundaryCondition (svOneDSolver) is only supported for " + "fluid, FSI, or CMM physics on face '") + + face_name + "'."); + } - // Create the coupled boundary condition object - if (use_cap) { lBc.coupled_bc = CoupledBoundaryCondition(coupled_bc_type, com_mod.msh[lBc.iM].fa[lBc.iFa], - com_mod.msh[lBc.iM].fa[lBc.iFa].name, zd_block, zerod_cap, - lEq.phys, cpl_flwP); + com_mod.msh[lBc.iM].fa[lBc.iFa].name, + /*block_name=*/"", lEq.phys, /*follower_pressure_load=*/false); + lBc.coupled_bc.set_oned_input_file(lBc.oned_input_file); + + } else if (com_mod.cplBC.svzerod_solver_interface.has_data) { + // ------------------------------------------------------------------ + // svZeroD path (existing). + // ------------------------------------------------------------------ + + // Sanity check: must be defined + if (!bc_params->coupling_interface.value_set) { + throw std::runtime_error( + std::string("[read_bc] CoupledBoundaryCondition requires for face '") + face_name + + "'."); + } + + // Sanity check: must be defined + if (!bc_params->coupling_interface.svzerod_solver_block.defined()) { + throw std::runtime_error( + std::string("[read_bc] must define for face '") + face_name + + "'."); + } + + // Get block name + const std::string zd_block = bc_params->coupling_interface.svzerod_solver_block.value(); + + // Get follower pressure load flag if defined + bool cpl_flwP = false; + if (lEq.phys == Equation_struct || lEq.phys == Equation_ustruct) { + cpl_flwP = bc_params->follower_pressure_load.value(); + } + + // Sanity check: CoupledBoundaryCondition is only supported for struct, ustruct, fluid, FSI, or CMM physics + const auto cpl_phys = lEq.phys; + if (cpl_phys != Equation_struct && cpl_phys != Equation_ustruct && cpl_phys != Equation_fluid && + cpl_phys != Equation_FSI && cpl_phys != Equation_CMM) { + throw std::runtime_error( + std::string("[read_bc] CoupledBoundaryCondition (svZeroDSolver) is only supported for struct, ustruct, fluid, FSI, or CMM physics on face '") + + face_name + "'."); + } + + // Sanity check: Follower pressure load must be used for 0D coupling with struct/ustruct + if ((cpl_phys == Equation_struct || cpl_phys == Equation_ustruct) && !cpl_flwP) { + throw std::runtime_error( + std::string("[read_bc] Follower pressure load must be used for 0D coupling with struct/ustruct on face '") + + face_name + "'."); + } + + // Get cap face VTP file name if defined + std::string zd_cap; + bool use_cap = false; + if (bc_params->coupling_interface.chamber_cap_surface.defined()) { + zd_cap = bc_params->coupling_interface.chamber_cap_surface.value(); + use_cap = true; + } + + if (use_cap) { + lBc.coupled_bc = CoupledBoundaryCondition(coupled_bc_type, com_mod.msh[lBc.iM].fa[lBc.iFa], + com_mod.msh[lBc.iM].fa[lBc.iFa].name, zd_block, zd_cap, + lEq.phys, cpl_flwP); + } else { + lBc.coupled_bc = CoupledBoundaryCondition(coupled_bc_type, com_mod.msh[lBc.iM].fa[lBc.iFa], + com_mod.msh[lBc.iM].fa[lBc.iFa].name, zd_block, lEq.phys, + cpl_flwP); + } } else { - lBc.coupled_bc = CoupledBoundaryCondition(coupled_bc_type, com_mod.msh[lBc.iM].fa[lBc.iFa], - com_mod.msh[lBc.iM].fa[lBc.iFa].name, zd_block, lEq.phys, - cpl_flwP); + throw std::runtime_error( + std::string("[read_bc] bType_Coupled is set on face '") + face_name + + "' but neither svZeroDSolver_interface nor svOneDSolver_interface is defined."); } } diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index a0006f8d6..c15542c3d 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -105,10 +105,16 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& } } - // Compute flowrates at 3D Neumann0D boundaries at timesteps n and n+1 for Coupled BCs + // Compute flowrates/pressures at 3D coupled boundaries for Coupled BCs if (utils::btest(bc.bType, iBC_Coupled)) { - bc.coupled_bc.compute_flowrates(com_mod, cm_mod, solutions); - #ifdef debug_calc_der_cpl_bc + if (cplBC.useSv1D && + bc.coupled_bc.get_bc_type() == consts::BoundaryConditionType::bType_Dir) { + // For svOneD DIR coupling the 1D solver needs the 3D face pressure. + bc.coupled_bc.compute_pressures(com_mod, cm_mod, solutions); + } else { + bc.coupled_bc.compute_flowrates(com_mod, cm_mod, solutions); + } + #ifdef debug_calc_der_cpl_bc dmsg << "iBC_Coupled "; dmsg << "coupled_bc.Qo: " << bc.coupled_bc.get_Qo(); dmsg << "coupled_bc.Qn: " << bc.coupled_bc.get_Qn(); @@ -275,7 +281,11 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& // Perturb flowrate and compute new pressure bc.coupled_bc.perturb_flowrate(diff); - svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); + if (cplBC.useSv1D) { + svOneD::calc_svOneD(com_mod, cm_mod, 'D'); + } else { + svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); + } // Finite difference: dP/dQ bc.r = (bc.coupled_bc.get_pressure() - orig_state.pressure) / diff; @@ -772,9 +782,15 @@ void set_bc_cpl(ComMod& com_mod, CmMod& cm_mod, const SolutionStates& solutions) } - // Compute flowrates at 3D Neumann0D boundaries at timesteps n and n+1 for Coupled BCs + // Compute flowrates/pressures at 3D coupled boundaries for Coupled BCs if (utils::btest(bc.bType, iBC_Coupled)) { - bc.coupled_bc.compute_flowrates(com_mod, cm_mod, solutions); + if (cplBC.useSv1D && + bc.coupled_bc.get_bc_type() == consts::BoundaryConditionType::bType_Dir) { + // For svOneD DIR coupling the 1D solver needs the 3D face pressure. + bc.coupled_bc.compute_pressures(com_mod, cm_mod, solutions); + } else { + bc.coupled_bc.compute_flowrates(com_mod, cm_mod, solutions); + } } if (ptr != -1) { diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index 3d9b8f85e..c0dea0572 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -43,7 +43,8 @@ // concurrently. // Phase 2 – batch result exchange: // - After every rank has finished solving, results are shared via -// MPI_Bcast so that ALL ranks know ALL cplBC.fa[i].y values. +// MPI_Bcast so that ALL ranks know the result, and each BC's +// coupled_bc.set_pressure() is updated accordingly. // // params array passed to run_1d_simulation_step_1d_: // params[0] = 2.0 (number of time points) @@ -72,9 +73,8 @@ namespace svOneD { // --------------------------------------------------------------------------- // Per-model state. Each entry corresponds to one svOneD-coupled face (one 1D -// model). Indexed by the sequential order in which faces were added to -// cplBC.fa (i.e., entry k corresponds to the k-th svOneD face, which has -// cplBCptr == face_ptr_list[k]). +// model). Indexed by the sequential order in which coupled faces were found +// in eq[0].bc[]. // --------------------------------------------------------------------------- struct OneDModelState { @@ -99,8 +99,8 @@ struct OneDModelState { // Owning MPI rank for this model. int owner_rank = 0; - // Index into cplBC.fa[] that this model services. - int fa_ptr = -1; + // Index into eq[0].bc[] for the BC this model services. + int iBc = -1; }; // --------------------------------------------------------------------------- @@ -148,21 +148,19 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) svOneDTime = com_mod.time; // ----- Collect the list of svOneD-coupled faces ----- - // We iterate over eq[0]'s BCs and pick those with iBC_cpl AND a non-empty - // oned_input_file. Their cplBCptr values are the indices into cplBC.fa[]. + // Iterate over eq[0]'s BCs and pick those with iBC_Coupled and a non-empty + // oned_input_file (stored in coupled_bc). { const int iEq = 0; const auto& eq = com_mod.eq[iEq]; for (int iBc = 0; iBc < eq.nBc; iBc++) { const auto& bc = eq.bc[iBc]; - int ptr = bc.cplBCptr; - if (ptr == -1) continue; - if (!utils::btest(bc.bType, iBC_cpl)) continue; - if (cplBC.fa[ptr].oned_input_file.empty()) continue; + if (!utils::btest(bc.bType, iBC_Coupled)) continue; + if (bc.coupled_bc.get_oned_input_file().empty()) continue; OneDModelState st; - st.fa_ptr = ptr; - st.coupling_type = (cplBC.fa[ptr].bGrp == CplBCType::cplBC_Neu) ? "NEU" : "DIR"; + st.iBc = iBc; + st.coupling_type = (bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) ? "NEU" : "DIR"; oned_models.push_back(std::move(st)); } } @@ -195,6 +193,8 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) // reading and initializing only the model(s) it owns. Rank k owns model k // (assigned via k % nProcs), so for N models and N ranks every rank handles // exactly one model with no inter-rank synchronization. + const int iEq = 0; + auto& eq = com_mod.eq[iEq]; for (int k = 0; k < nTotalModels; k++) { auto& st = oned_models[k]; st.owner_rank = k % nProcs; @@ -202,7 +202,7 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) if (myRank != st.owner_rank) continue; // This rank owns model k: read the input file and initialize. - const std::string& input_file = cplBC.fa[st.fa_ptr].oned_input_file; + const std::string& input_file = eq.bc[st.iBc].coupled_bc.get_oned_input_file(); int problem_id = 0; int system_size = 0; @@ -219,8 +219,8 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) st.solution.resize(system_size, 0.0); shared_lib_instance->return_solution(problem_id, st.solution.data(), system_size); - // Initial cplBC.fa y = 0; first calc_svOneD call sets the real value. - cplBC.fa[st.fa_ptr].y = 0.0; + // Initial coupled value = 0; first calc_svOneD call sets the real value. + eq.bc[st.iBc].coupled_bc.set_pressure(0.0); } // ----- Broadcast metadata for all models (Phase 2: batch exchange) ----- @@ -246,7 +246,6 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) { using namespace consts; - auto& cplBC = com_mod.cplBC; auto& cm = com_mod.cm; const int myRank = cm.taskId; @@ -254,6 +253,9 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) const double t_new = svOneDTime + com_mod.dt; const int nTotalModels = static_cast(oned_models.size()); + const int iEq = 0; + auto& eq = com_mod.eq[iEq]; + // ----- Phase 1: each rank runs its own models without blocking ----- // All ranks proceed through this loop simultaneously, each executing only // the models it owns. No MPI call here, so model k on rank A and model k+1 @@ -261,8 +263,8 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) std::vector cpl_values(nTotalModels, 0.0); for (int k = 0; k < nTotalModels; k++) { - auto& st = oned_models[k]; - int fa_ptr = st.fa_ptr; + auto& st = oned_models[k]; + auto& bc = eq.bc[st.iBc]; if (myRank != st.owner_rank) continue; @@ -272,12 +274,12 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) params[1] = t_old; params[2] = t_new; - if (cplBC.fa[fa_ptr].bGrp == CplBCType::cplBC_Neu) { - params[3] = cplBC.fa[fa_ptr].Qo; - params[4] = cplBC.fa[fa_ptr].Qn; + if (bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) { + params[3] = bc.coupled_bc.get_Qo(); + params[4] = bc.coupled_bc.get_Qn(); } else { - params[3] = cplBC.fa[fa_ptr].Po; - params[4] = cplBC.fa[fa_ptr].Pn; + params[3] = bc.coupled_bc.get_Po(); + params[4] = bc.coupled_bc.get_Pn(); } // Working copy of solution so that 'D' steps don't corrupt the @@ -295,7 +297,7 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) if (error_code != 0) { throw std::runtime_error( "[svOneD::calc_svOneD] 1D solver step for face '" + - cplBC.fa[fa_ptr].name + "' failed with error code " + + bc.coupled_bc.get_oned_input_file() + "' failed with error code " + std::to_string(error_code)); } @@ -305,14 +307,14 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) } } - // ----- Phase 2: broadcast all results and update cplBC.fa[].y ----- + // ----- Phase 2: broadcast all results and update coupled BCs ----- // After every rank has finished solving its own models, gather the results. // Each MPI_Bcast here is a cheap scalar transfer; the expensive 1D solver // work has already been done concurrently in Phase 1. for (int k = 0; k < nTotalModels; k++) { auto& st = oned_models[k]; MPI_Bcast(&cpl_values[k], 1, MPI_DOUBLE, st.owner_rank, cm.com()); - cplBC.fa[st.fa_ptr].y = cpl_values[k]; + eq.bc[st.iBc].coupled_bc.set_pressure(cpl_values[k]); } // Advance the simulation clock after the final iteration. From f3d398fdfa81acd4efec5f9852f3a0d0389a87a3 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:35 -0700 Subject: [PATCH 13/51] Fix missing Qo_ in copy/move constructors and assignment operators --- Code/Source/solver/CoupledBoundaryCondition.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Code/Source/solver/CoupledBoundaryCondition.cpp b/Code/Source/solver/CoupledBoundaryCondition.cpp index 7c4f035b8..3eff0a60f 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.cpp +++ b/Code/Source/solver/CoupledBoundaryCondition.cpp @@ -55,6 +55,7 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(const CoupledBound block_name_ = other.block_name_; face_name_ = other.face_name_; oned_input_file_ = other.oned_input_file_; + Qo_ = other.Qo_; Qn_ = other.Qn_; Po_ = other.Po_; Pn_ = other.Pn_; @@ -85,6 +86,7 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(CoupledBoundaryCondition&& ot , block_name_(std::move(other.block_name_)) , face_name_(std::move(other.face_name_)) , oned_input_file_(std::move(other.oned_input_file_)) + , Qo_(other.Qo_) , Qn_(other.Qn_) , Po_(other.Po_) , Pn_(other.Pn_) @@ -131,6 +133,7 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(CoupledBoundaryCon block_name_ = std::move(other.block_name_); face_name_ = std::move(other.face_name_); oned_input_file_ = std::move(other.oned_input_file_); + Qo_ = other.Qo_; Qn_ = other.Qn_; Po_ = other.Po_; Pn_ = other.Pn_; From 73f5d911e43369ee7075f4b31298245007a44619 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:36 -0700 Subject: [PATCH 14/51] Fix library name typo in svOneD_subroutines.cpp comment --- Code/Source/solver/svOneD_subroutines.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index c0dea0572..91758a87b 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -5,7 +5,7 @@ // // These routines interface the 3D finite-element solver (svMultiPhysics) with // the 1D blood-flow solver (svOneDSolver) via a dynamically loaded shared -// library (libsvoned_interface.so/.dylib). +// library (libsvOneDSolver_interface.so/.dylib). // // Coupling overview // ----------------- From 07883f646638796a81d56392b6274a58e73edbf1 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:36 -0700 Subject: [PATCH 15/51] fix DIR coupling bug: set_bc.cpp uses get_Qn() for DIR, svOneD calls set_flowrates() for DIR --- Code/Source/solver/set_bc.cpp | 11 +++++++++-- Code/Source/solver/svOneD_subroutines.cpp | 11 ++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index c15542c3d..d606536eb 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -852,9 +852,16 @@ void set_bc_cpl(ComMod& com_mod, CmMod& cm_mod, const SolutionStates& solutions) auto& bc = eq.bc[iBc]; int iFa = bc.iFa; - // For Coupled BC, get pressure from CoupledBoundaryCondition (set by svZeroD interface) + // For Coupled BC, get the coupling value from CoupledBoundaryCondition: + // NEU: 0D/1D solver returns pressure P → apply as Neumann traction + // DIR: 0D/1D solver returns flow rate Q → apply as Dirichlet velocity (bc.g = Q, then + // set_bc_dir_l multiplies by gx(a)*nV to produce the nodal velocity profile) if (utils::btest(bc.bType, iBC_Coupled)) { - bc.g = bc.coupled_bc.get_pressure(); + if (bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Dir) { + bc.g = bc.coupled_bc.get_Qn(); + } else { + bc.g = bc.coupled_bc.get_pressure(); + } } // For other coupled BCs (Dir, Neu), get from cplBC.fa else { diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index 91758a87b..ddafaa9e2 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -314,7 +314,16 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) for (int k = 0; k < nTotalModels; k++) { auto& st = oned_models[k]; MPI_Bcast(&cpl_values[k], 1, MPI_DOUBLE, st.owner_rank, cm.com()); - eq.bc[st.iBc].coupled_bc.set_pressure(cpl_values[k]); + auto& cpl_bc = eq.bc[st.iBc].coupled_bc; + if (cpl_bc.get_bc_type() == BoundaryConditionType::bType_Dir) { + // 1D solver returns flow Q for DIR coupling; store it as flowrate so that + // set_bc can read get_Qn() and build the nodal velocity profile. + double Qo_prev = cpl_bc.get_Qn(); + cpl_bc.set_flowrates(Qo_prev, cpl_values[k]); + } else { + // 1D solver returns pressure P for NEU coupling. + cpl_bc.set_pressure(cpl_values[k]); + } } // Advance the simulation clock after the final iteration. From e7b5135ca05d2264f288dbb5cc920ac3f664ed9c Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:37 -0700 Subject: [PATCH 16/51] fix DIR Coupled BCs: route to set_bc_dir_l (velocity), exclude from set_bc_neu_l (pressure) --- Code/Source/solver/set_bc.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index d606536eb..64e7ba2da 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -938,7 +938,12 @@ void set_bc_dir(ComMod& com_mod, SolutionStates& solutions) } } // END bType_CMM - if (!utils::btest(bc.bType, iBC_Dir)) { + // Allow Coupled BCs whose internal coupling type is DIR (svZeroD/svOneD DIR + // coupling): iBC_Dir is cleared for these in read_files but the velocity + // profile must still be applied via set_bc_dir_l. + bool isCoupledDir = utils::btest(bc.bType, iBC_Coupled) && + (bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Dir); + if (!utils::btest(bc.bType, iBC_Dir) && !isCoupledDir) { continue; } @@ -1447,7 +1452,11 @@ void set_bc_neu(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& solu if (utils::btest(bc.bType, iBC_Ris0D)) {continue;} - if (utils::btest(bc.bType, iBC_Neu) || utils::btest(bc.bType, iBC_Coupled)) { + // Coupled BCs with DIR type must be handled by set_bc_dir (velocity profile), + // not here. Only NEU Coupled BCs get a Neumann pressure traction. + bool isCoupledDir = utils::btest(bc.bType, iBC_Coupled) && + (bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Dir); + if ((utils::btest(bc.bType, iBC_Neu) || utils::btest(bc.bType, iBC_Coupled)) && !isCoupledDir) { #ifdef debug_set_bc_neu dmsg << "iM: " << iM+1; dmsg << "iFa: " << iFa+1; From 852b821de569f3fa759eb36291177037fef889fa Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:38 -0700 Subject: [PATCH 17/51] fix Bug 5: compute_pressures() for any DIR Coupled BC (not just svOneD), fixes svZeroD DIR --- Code/Source/solver/set_bc.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index 64e7ba2da..29fad6f43 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -104,12 +104,13 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& RCRflag = true; } } - - // Compute flowrates/pressures at 3D coupled boundaries for Coupled BCs + + // Compute flowrates/pressures at 3D coupled boundaries for Coupled BCs. + // DIR coupling (both svZeroD and svOneD): the downstream solver is driven + // by the 3D face average pressure, so compute_pressures() is required. + // NEU coupling: the downstream solver is driven by the 3D outflow Q. if (utils::btest(bc.bType, iBC_Coupled)) { - if (cplBC.useSv1D && - bc.coupled_bc.get_bc_type() == consts::BoundaryConditionType::bType_Dir) { - // For svOneD DIR coupling the 1D solver needs the 3D face pressure. + if (bc.coupled_bc.get_bc_type() == consts::BoundaryConditionType::bType_Dir) { bc.coupled_bc.compute_pressures(com_mod, cm_mod, solutions); } else { bc.coupled_bc.compute_flowrates(com_mod, cm_mod, solutions); @@ -781,12 +782,12 @@ void set_bc_cpl(ComMod& com_mod, CmMod& cm_mod, const SolutionStates& solutions) } } - - // Compute flowrates/pressures at 3D coupled boundaries for Coupled BCs + // Compute flowrates/pressures at 3D coupled boundaries for Coupled BCs. + // DIR coupling (both svZeroD and svOneD): the downstream solver is driven + // by the 3D face average pressure, so compute_pressures() is required. + // NEU coupling: the downstream solver is driven by the 3D outflow Q. if (utils::btest(bc.bType, iBC_Coupled)) { - if (cplBC.useSv1D && - bc.coupled_bc.get_bc_type() == consts::BoundaryConditionType::bType_Dir) { - // For svOneD DIR coupling the 1D solver needs the 3D face pressure. + if (bc.coupled_bc.get_bc_type() == consts::BoundaryConditionType::bType_Dir) { bc.coupled_bc.compute_pressures(com_mod, cm_mod, solutions); } else { bc.coupled_bc.compute_flowrates(com_mod, cm_mod, solutions); From 5119968f76435b9628a401b78b37276ece02616a Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:39 -0700 Subject: [PATCH 18/51] fix Bug 6: auto-enforce bType_flx for DIR Coupled BCs so Q/area gives m/s velocity --- Code/Source/solver/read_files.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 8e30ef82d..0811b623d 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -538,6 +538,17 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_flx)); } + // For DIR Coupled BCs (svZeroD or svOneD), the downstream solver always returns a + // volumetric flow rate Q [m³/s], not a velocity [m/s]. Without bType_flx, bc_ini + // sets gx(a) = 1, and set_bc_dir_l applies velocity = Q * nV which has the wrong + // dimensions. With bType_flx, bc_ini normalises gx(a) = 1/area, so the applied + // velocity = (Q/area) * nV [m/s] is physically correct. Enforce this automatically + // regardless of the user's setting. + if (utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Coupled)) && + coupled_bc_type == BoundaryConditionType::bType_Dir) { + lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_flx)); + } + // To zero-out perimeter or not. Default is .true. for Dir/CMM // ltmp = false; From 2f497426c653c0b67fc23e598feb27e31e4bf310 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:39 -0700 Subject: [PATCH 19/51] feat: enable svZeroD (0D) + RCR coexistence, mirroring svOneD behavior --- Code/Source/solver/set_bc.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index 29fad6f43..03f364831 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -181,6 +181,10 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& set_bc::genBC_Integ_X(com_mod, cm_mod, "D"); } else if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); + // Also integrate any RCR faces that coexist with svZeroD faces. + if (RCRflag) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } } else if (cplBC.useSv1D) { svOneD::calc_svOneD(com_mod, cm_mod, 'D'); // Also integrate any RCR faces that coexist with svOneD faces. @@ -235,6 +239,10 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& set_bc::genBC_Integ_X(com_mod, cm_mod, "D"); } else if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); + // Also integrate any RCR faces that coexist with svZeroD faces. + if (RCRflag) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } } else if (cplBC.useSv1D) { svOneD::calc_svOneD(com_mod, cm_mod, 'D'); // Also integrate any RCR faces that coexist with svOneD faces. @@ -838,6 +846,10 @@ void set_bc_cpl(ComMod& com_mod, CmMod& cm_mod, const SolutionStates& solutions) set_bc::genBC_Integ_X(com_mod, cm_mod, "D"); } else if (cplBC.useSvZeroD){ svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); + // Also integrate any RCR faces that coexist with svZeroD faces. + if (RCRflag) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } } else if (cplBC.useSv1D) { svOneD::calc_svOneD(com_mod, cm_mod, 'D'); // Also integrate any RCR faces that coexist with svOneD faces. From 0c012dc01070f703179f183a5966a48f75156cb8 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:40 -0700 Subject: [PATCH 20/51] fix: allow RCR BC to coexist with svZeroD coupling, fix scheme overwrite --- Code/Source/solver/read_files.cpp | 10 +++++----- Code/Source/solver/txt.cpp | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 0811b623d..661b7b2a3 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -351,9 +351,9 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, lBc.RCR.Pd = bc_params->rcr.distal_pressure.value(); lBc.RCR.Xo = bc_params->rcr.initial_pressure.value(); - if ((com_mod.cplBC.schm != CplBCType::cplBC_NA && !com_mod.cplBC.useSv1D) || + if ((com_mod.cplBC.schm != CplBCType::cplBC_NA && !com_mod.cplBC.useSv1D && !com_mod.cplBC.useSvZeroD) || com_mod.cplBC.xo.size() != 0) { - throw std::runtime_error("[read_bc] RCR cannot be used in conjunction with cplBC (except alongside svOneD)."); + throw std::runtime_error("[read_bc] RCR cannot be used in conjunction with cplBC (except alongside svOneD or svZeroD)."); } com_mod.cplBC.nFa = com_mod.cplBC.nFa + 1; lBc.cplBCptr = com_mod.cplBC.nFa - 1; @@ -1625,9 +1625,9 @@ void read_eq(Simulation* simulation, EquationParameters* eq_params, eqType& lEq) if (std::set{Equation_fluid,Equation_FSI,Equation_CMM}.count(lEq.phys) == 0) { throw std::runtime_error("RCR-type BC is allowed for fluid/CMM/FSI eq. only."); } - // Only set coupling scheme if not already configured by an external solver (e.g. svOneD). - // When svOneD and RCR coexist, svOneD owns the scheme; RCR uses the same scheme. - if (!cplBC.useSv1D) { + // Only set coupling scheme if not already configured by an external solver (e.g. svOneD, svZeroD). + // When svOneD/svZeroD and RCR coexist, the external solver owns the scheme; RCR uses the same scheme. + if (!cplBC.useSv1D && !cplBC.useSvZeroD) { cplBC.schm = CplBCType::cplBC_SI; if (lEq.useTLS) { cplBC.schm = CplBCType::cplBC_E; diff --git a/Code/Source/solver/txt.cpp b/Code/Source/solver/txt.cpp index 9d02be893..3c93d882d 100644 --- a/Code/Source/solver/txt.cpp +++ b/Code/Source/solver/txt.cpp @@ -177,6 +177,16 @@ void txt(Simulation* simulation, const bool init_write, const SolutionStates& so set_bc::genBC_Integ_X(com_mod, cm_mod, "L"); } else if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'L'); + // Also integrate any RCR faces that coexist with svZeroD faces. + for (auto& bc : com_mod.eq[0].bc) { + if (utils::btest(bc.bType, iBC_RCR)) { + ltmp = true; + break; + } + } + if (ltmp) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } } else if (cplBC.useSv1D) { svOneD::calc_svOneD(com_mod, cm_mod, 'L'); From c74ceebccde33722a4bf12c9116fd1cc439ac8cf Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:41 -0700 Subject: [PATCH 21/51] fix: distribute cplBC.xo to slave processes when svZeroD+RCR coexist --- Code/Source/solver/distribute.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Code/Source/solver/distribute.cpp b/Code/Source/solver/distribute.cpp index 234b27c74..920a0d574 100644 --- a/Code/Source/solver/distribute.cpp +++ b/Code/Source/solver/distribute.cpp @@ -557,11 +557,16 @@ void distribute(Simulation* simulation) cplBC.xo.resize(cplBC.nX); } - } else if (cplBC.useSvZeroD) { - if (cm.slv(cm_mod)) { - cplBC.nX = 0; + } else if (cplBC.useSvZeroD) { + // Broadcast nX and xo: when RCR faces coexist with svZeroD, nX > 0 and xo must + // be distributed to all slave processes so rcr_init can access cplBC.xo[ptr]. + cm.bcast(cm_mod, &cplBC.nX); + if (cplBC.xo.size() == 0) { cplBC.xo.resize(cplBC.nX); } + if (cplBC.nX != 0) { + cm.bcast(cm_mod, cplBC.xo); + } } else { // RCR (Windkessel): nX/xo sized in read_files from nFa; not genBC/svZeroD. From 24adc2e265bb99ad715d1653bff6c40d217a73d8 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:42 -0700 Subject: [PATCH 22/51] fix: broadcast useSv1D to slaves and add RCR integration in sv1D txt.cpp branch --- Code/Source/solver/distribute.cpp | 1 + Code/Source/solver/txt.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Code/Source/solver/distribute.cpp b/Code/Source/solver/distribute.cpp index 920a0d574..5ed3a7bcb 100644 --- a/Code/Source/solver/distribute.cpp +++ b/Code/Source/solver/distribute.cpp @@ -550,6 +550,7 @@ void distribute(Simulation* simulation) cm.bcast_enum(cm_mod, &cplBC.schm); cm.bcast(cm_mod, &cplBC.useGenBC); cm.bcast(cm_mod, &cplBC.useSvZeroD); + cm.bcast(cm_mod, &cplBC.useSv1D); if (cplBC.useGenBC) { if (cm.slv(cm_mod)) { diff --git a/Code/Source/solver/txt.cpp b/Code/Source/solver/txt.cpp index 3c93d882d..9aba6afa1 100644 --- a/Code/Source/solver/txt.cpp +++ b/Code/Source/solver/txt.cpp @@ -190,7 +190,17 @@ void txt(Simulation* simulation, const bool init_write, const SolutionStates& so } else if (cplBC.useSv1D) { svOneD::calc_svOneD(com_mod, cm_mod, 'L'); - + // Also integrate any RCR faces that coexist with svOneD faces. + for (auto& bc : com_mod.eq[0].bc) { + if (utils::btest(bc.bType, iBC_RCR)) { + ltmp = true; + break; + } + } + if (ltmp) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } + } else { for (auto& bc : com_mod.eq[0].bc) { if (utils::btest(bc.bType, iBC_RCR)) { From 7c4f484a4c75d6d716bd595ac716e23cc488c630 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:42 -0700 Subject: [PATCH 23/51] fix: broadcast sv1d_solver_interface data to all MPI ranks in distribute.cpp --- Code/Source/solver/distribute.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Code/Source/solver/distribute.cpp b/Code/Source/solver/distribute.cpp index 5ed3a7bcb..4e06ecfc4 100644 --- a/Code/Source/solver/distribute.cpp +++ b/Code/Source/solver/distribute.cpp @@ -568,12 +568,30 @@ void distribute(Simulation* simulation) if (cplBC.nX != 0) { cm.bcast(cm_mod, cplBC.xo); } + + } else if (cplBC.useSv1D) { + // Broadcast the sv1D solver interface data so that ALL ranks can call + // init_svOneD / calc_svOneD (which use MPI_Bcast collectives that require + // every rank to participate). Without this, slave processes have + // has_data = false and throw immediately inside init_svOneD. + cm.bcast(cm_mod, &cplBC.sv1d_solver_interface.has_data); + cm.bcast(cm_mod, cplBC.sv1d_solver_interface.solver_library); + + // Broadcast nX and xo: when RCR faces coexist with svOneD, nX > 0 and xo + // must be distributed to all slave processes so rcr_init works correctly. + cm.bcast(cm_mod, &cplBC.nX); + if (cplBC.xo.size() == 0) { + cplBC.xo.resize(cplBC.nX); + } + if (cplBC.nX != 0) { + cm.bcast(cm_mod, cplBC.xo); + } } else { - // RCR (Windkessel): nX/xo sized in read_files from nFa; not genBC/svZeroD. + // RCR (Windkessel): nX/xo sized in read_files from nFa; not genBC/svZeroD/svOneD. cm.bcast(cm_mod, &cplBC.nX); if (cplBC.xo.size() == 0) { - cplBC.xo.resize(cplBC.nX); + cplBC.xo.resize(cplBC.nX); } if (cplBC.nX != 0) { cm.bcast(cm_mod, cplBC.xo); From 283f6686359cc0ecefdd2dc9e0c6d806958925ea Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:43 -0700 Subject: [PATCH 24/51] fix: update run_1d_simulation_step_1d to pass last_flag and save_incr --- .../svOneD_interface/OneDSolverInterface.cpp | 16 +++++++++++----- .../svOneD_interface/OneDSolverInterface.h | 12 ++++++++---- Code/Source/solver/svOneD_subroutines.cpp | 6 +++--- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp b/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp index bae1c9ec1..da6e098c2 100644 --- a/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp +++ b/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp @@ -84,17 +84,23 @@ void OneDSolverInterface::update_solution(int problem_id, double* solution, int } void OneDSolverInterface::run_step(int problem_id, double current_time, - int save_flag, + int save_incr, const std::string& coupling_type, double* params, double* solution, - double& cpl_value, int& error_code) + double& cpl_value, char last_flag, + int& error_code) { if (!run_1d_simulation_step_1d_) { throw std::runtime_error("[OneDSolverInterface] run_1d_simulation_step_1d not loaded"); } - run_1d_simulation_step_1d_(problem_id, current_time, save_flag, - coupling_type.c_str(), params, solution, - cpl_value, error_code); + // Copy coupling_type into a mutable buffer (shared-library uses char*). + std::vector ctype_buf(coupling_type.begin(), coupling_type.end()); + ctype_buf.push_back('\0'); + // Copy last_flag into a mutable single-character buffer. + char flag_buf[2] = { last_flag, '\0' }; + run_1d_simulation_step_1d_(problem_id, current_time, save_incr, + ctype_buf.data(), params, solution, + cpl_value, flag_buf, error_code); } void OneDSolverInterface::extract_coupled_dof(int problem_id, int& coupled_dof, diff --git a/Code/Source/solver/svOneD_interface/OneDSolverInterface.h b/Code/Source/solver/svOneD_interface/OneDSolverInterface.h index 974589d38..29fcb3dd3 100644 --- a/Code/Source/solver/svOneD_interface/OneDSolverInterface.h +++ b/Code/Source/solver/svOneD_interface/OneDSolverInterface.h @@ -50,16 +50,20 @@ class OneDSolverInterface { /// @brief Advance the 1D solver by one time step. /// @param problem_id Problem identifier. /// @param current_time Current simulation time (start of the step). - /// @param save_flag Non-zero to write VTK output for this step. + /// @param save_incr VTK output interval (Increment_in_saving_VTK_files from solver.xml); + /// the 1D library decides internally whether to write output. /// @param coupling_type "NEU" or "DIR". /// @param params Array [N, t1, t2, ..., val1, val2, ...] where N=2. /// @param solution In/out: solution vector updated after the step. /// @param cpl_value Output: the BC value returned by the 1D solver /// (pressure for NEU, flow for DIR). + /// @param last_flag 'L' for the final (committed) iteration, 'D' for + /// derivative / predictor steps. /// @param error_code Output: non-zero on failure. - void run_step(int problem_id, double current_time, int save_flag, + void run_step(int problem_id, double current_time, int save_incr, const std::string& coupling_type, double* params, - double* solution, double& cpl_value, int& error_code); + double* solution, double& cpl_value, char last_flag, + int& error_code); /// @brief Retrieve the index within the solution vector that corresponds to /// the coupled boundary DOF. @@ -79,7 +83,7 @@ class OneDSolverInterface { void (*return_1d_solution_)(int, double*, int) = nullptr; void (*update_1d_solution_)(int, double*, int) = nullptr; void (*run_1d_simulation_step_1d_)(int, double, int, const char*, double*, - double*, double&, int&) = nullptr; + double*, double&, char*, int&) = nullptr; void (*extract_coupled_dof_)(int, int&, char*) = nullptr; }; diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index ddafaa9e2..054a61f6b 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -287,12 +287,12 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) std::vector work_sol = st.solution; st.interface->update_solution(st.problem_id, work_sol.data(), st.system_size); - int save_flag = (BCFlag == 'L') ? 1 : 0; + int save_incr = com_mod.saveIncr; int error_code = 0; - st.interface->run_step(st.problem_id, t_old, save_flag, + st.interface->run_step(st.problem_id, t_old, save_incr, st.coupling_type, params, - work_sol.data(), cpl_values[k], error_code); + work_sol.data(), cpl_values[k], BCFlag, error_code); if (error_code != 0) { throw std::runtime_error( From 81c9a318cf0bd34be9e6015afa76252df6844d9d Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:44 -0700 Subject: [PATCH 25/51] fix: recompute NEU coupling Qn_ from converged Yn before 1D L-step commit --- Code/Source/solver/txt.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Code/Source/solver/txt.cpp b/Code/Source/solver/txt.cpp index 9aba6afa1..76f6b1308 100644 --- a/Code/Source/solver/txt.cpp +++ b/Code/Source/solver/txt.cpp @@ -189,6 +189,20 @@ void txt(Simulation* simulation, const bool init_write, const SolutionStates& so } } else if (cplBC.useSv1D) { + // Update NEU coupling flowrates from the final converged velocity + // field (Yn) before committing the 1D solution. During the Newton + // loop, compute_flowrates() is called at the *start* of each + // iteration using a partially-converged Yn. After the last picc + // correction Yn is fully converged, but Qn_ is never refreshed. + // Re-computing here ensures the 1D solver receives the same + // flowrate that write_boundary_integral_data() reports in + // B_NS_Velocity_flux.txt. + for (auto& bc : com_mod.eq[0].bc) { + if (utils::btest(bc.bType, iBC_Coupled) && + bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) { + bc.coupled_bc.compute_flowrates(com_mod, cm_mod); + } + } svOneD::calc_svOneD(com_mod, cm_mod, 'L'); // Also integrate any RCR faces that coexist with svOneD faces. for (auto& bc : com_mod.eq[0].bc) { From 543d0a4023616e4cfcf2317697ab77059cceeff0 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:45 -0700 Subject: [PATCH 26/51] fix: negate 1D solver flow for DIR coupling to match svZeroD sign convention --- Code/Source/solver/svOneD_subroutines.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index 054a61f6b..0dbc7bd36 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -318,8 +318,13 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) if (cpl_bc.get_bc_type() == BoundaryConditionType::bType_Dir) { // 1D solver returns flow Q for DIR coupling; store it as flowrate so that // set_bc can read get_Qn() and build the nodal velocity profile. + // Negate the sign: the 1D solver returns Q > 0 for inflow, but the 3D + // code applies velocity as Q * gx * outward_normal, so Q must be negative + // for an inlet face (outward normal points away from the domain). + // This matches the svZeroD convention: in_out = -1 for DIR (outlet of 0D + // = inlet of 3D), giving QCoupled = -1 * lpn_state_y[flow_id]. double Qo_prev = cpl_bc.get_Qn(); - cpl_bc.set_flowrates(Qo_prev, cpl_values[k]); + cpl_bc.set_flowrates(Qo_prev, -cpl_values[k]); } else { // 1D solver returns pressure P for NEU coupling. cpl_bc.set_pressure(cpl_values[k]); From a0ed4cac6451c23b709ec41267081856cdfa1fc1 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:45 -0700 Subject: [PATCH 27/51] fix: set bType_zp for Coupled-DIR BCs to preserve flow rate --- Code/Source/solver/read_files.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 661b7b2a3..07a4c8d29 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -549,10 +549,20 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_flx)); } - // To zero-out perimeter or not. Default is .true. for Dir/CMM + // To zero-out perimeter or not. Default is .true. for Dir/CMM and Coupled-DIR. + // For Coupled-DIR BCs, bType_Dir is cleared, but we still need bType_zp so that + // bc_ini zeros shared perimeter nodes before normalising gx. Without bType_zp, + // gx = 1/full_area and the wall BC later zeros perimeter nodes, reducing the + // effective flux to Q*(interior_area/full_area) instead of Q. // ltmp = false; ltmp = utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Dir)); + // Also enable zero-perimeter for Coupled-DIR (svZeroD or svOneD DIR coupling). + if (!ltmp && + utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Coupled)) && + coupled_bc_type == BoundaryConditionType::bType_Dir) { + ltmp = true; + } if (bc_params->zero_out_perimeter.defined()) { ltmp = bc_params->zero_out_perimeter.value(); } From 8327f4c933ec806cccc34cb40f0023b823845bc7 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:47 -0700 Subject: [PATCH 28/51] fix: register Coupled-DIR face as BC_TYPE_Dir in fsi_ls_ini to exclude DOFs from linear solve For 1D Dirichlet coupling, `iBC_Dir` is cleared in read_files.cpp so that set_bc_cpl routes the BC correctly. However this caused fsi_ls_ini to skip registering the face with the FSILS linear solver as a Dirichlet (BC_TYPE_Dir) constraint. As a result, the preconditioner did not zero out those rows/columns, and the linear solver overwrote the velocity values applied by set_bc_dir with the NS solution, producing a lower-than-expected centerline velocity (~47 vs ~63.66). Fix: detect Coupled-DIR faces in fsi_ls_ini and register them as BC_TYPE_Dir so their DOFs are properly excluded from Ax=b. --- Code/Source/solver/baf_ini.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Code/Source/solver/baf_ini.cpp b/Code/Source/solver/baf_ini.cpp index 4f18f177f..3fd00789c 100644 --- a/Code/Source/solver/baf_ini.cpp +++ b/Code/Source/solver/baf_ini.cpp @@ -776,10 +776,21 @@ void fsi_ls_ini(ComMod& com_mod, const CmMod& cm_mod, bcType& lBc, const faceTyp } fsils_bc_create(com_mod.lhs, lsPtr, lFa.nNo, nsd, BcType::BC_TYPE_Dir, gNodes, sVl); } + + } else if (btest(lBc.bType, iBC_Neu) || btest(lBc.bType, iBC_Coupled)) { + // For Coupled-DIR BCs: iBC_Dir was cleared in read_files but the face DOFs must still + // be excluded from the linear solve (Ax=b). Register as BC_TYPE_Dir so the + // preconditioner zeros out those rows/columns, preventing the solver from + // overwriting the velocity values set by set_bc_dir. + if (btest(lBc.bType, iBC_Coupled) && + lBc.coupled_bc.get_bc_type() == consts::BoundaryConditionType::bType_Dir) { + lsPtr = lsPtr + 1; + lBc.lsPtr = lsPtr; + sVl = 0.0; + fsils_bc_create(com_mod.lhs, lsPtr, lFa.nNo, nsd, BcType::BC_TYPE_Dir, gNodes, sVl); - } else if (btest(lBc.bType, iBC_Neu)) { // Compute integral of normal vector over the face (needed for resistance BC/0D-coupling) - if (btest(lBc.bType, iBC_res)) { + } else if (btest(lBc.bType, iBC_res)) { sV = 0.0; for (int e = 0; e < lFa.nEl; e++) { if (lFa.eType == ElementType::NRB) { From ac5eb225b1215e38ad85cc66496baa5bf4eb59cb Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:48 -0700 Subject: [PATCH 29/51] feat: add pressure ramp for 1D coupling initialization (Method 2) - CouplingInterfaceParameters: add Coupling_ramp_steps (int) and Coupling_ramp_ref_pressure (double) XML parameters - CoupledBoundaryCondition: add oned_ramp_steps_ / oned_ramp_ref_pressure_ private members with set_oned_ramp() / get_oned_ramp_steps() / get_oned_ramp_ref_pressure() accessors; propagate through all copy/move constructors and assignment operators - read_files.cpp: read ramp params from CouplingInterfaceParameters and store them in lBc.coupled_bc via set_oned_ramp() - svOneD_subroutines.cpp: OneDModelState gains ramp_steps, ramp_ref_pressure, step_count fields; init_svOneD reads them from coupled_bc; calc_svOneD applies linear pressure ramp for DIR coupling; step_count is incremented on every committed (BCFlag=='L') step --- .../solver/CoupledBoundaryCondition.cpp | 8 ++++++ Code/Source/solver/CoupledBoundaryCondition.h | 17 ++++++++++++ Code/Source/solver/Parameters.cpp | 2 ++ Code/Source/solver/Parameters.h | 7 +++++ Code/Source/solver/read_files.cpp | 10 +++++++ Code/Source/solver/svOneD_subroutines.cpp | 26 +++++++++++++++++-- 6 files changed, 68 insertions(+), 2 deletions(-) diff --git a/Code/Source/solver/CoupledBoundaryCondition.cpp b/Code/Source/solver/CoupledBoundaryCondition.cpp index 3eff0a60f..132415a20 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.cpp +++ b/Code/Source/solver/CoupledBoundaryCondition.cpp @@ -23,6 +23,8 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(const CoupledBoundaryConditio , block_name_(other.block_name_) , face_name_(other.face_name_) , oned_input_file_(other.oned_input_file_) + , oned_ramp_steps_(other.oned_ramp_steps_) + , oned_ramp_ref_pressure_(other.oned_ramp_ref_pressure_) , Qo_(other.Qo_) , Qn_(other.Qn_) , Po_(other.Po_) @@ -55,6 +57,8 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(const CoupledBound block_name_ = other.block_name_; face_name_ = other.face_name_; oned_input_file_ = other.oned_input_file_; + oned_ramp_steps_ = other.oned_ramp_steps_; + oned_ramp_ref_pressure_ = other.oned_ramp_ref_pressure_; Qo_ = other.Qo_; Qn_ = other.Qn_; Po_ = other.Po_; @@ -86,6 +90,8 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(CoupledBoundaryCondition&& ot , block_name_(std::move(other.block_name_)) , face_name_(std::move(other.face_name_)) , oned_input_file_(std::move(other.oned_input_file_)) + , oned_ramp_steps_(other.oned_ramp_steps_) + , oned_ramp_ref_pressure_(other.oned_ramp_ref_pressure_) , Qo_(other.Qo_) , Qn_(other.Qn_) , Po_(other.Po_) @@ -133,6 +139,8 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(CoupledBoundaryCon block_name_ = std::move(other.block_name_); face_name_ = std::move(other.face_name_); oned_input_file_ = std::move(other.oned_input_file_); + oned_ramp_steps_ = other.oned_ramp_steps_; + oned_ramp_ref_pressure_ = other.oned_ramp_ref_pressure_; Qo_ = other.Qo_; Qn_ = other.Qn_; Po_ = other.Po_; diff --git a/Code/Source/solver/CoupledBoundaryCondition.h b/Code/Source/solver/CoupledBoundaryCondition.h index 4e376a41a..b01d54324 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.h +++ b/Code/Source/solver/CoupledBoundaryCondition.h @@ -193,6 +193,9 @@ class CoupledBoundaryCondition { std::string face_name_; ///< Face name from the mesh /// @brief svOneD coupling data std::string oned_input_file_; ///< Path to svOneDSolver input file (empty for svZeroD BCs) + /// @brief Pressure ramp parameters for 1D coupling initialization (DIR coupling only). + int oned_ramp_steps_ = 0; ///< Number of ramp steps (0 = disabled) + double oned_ramp_ref_pressure_ = 0.0; ///< Reference pressure at step 0 /// @brief Flowrate data double Qo_ = 0.0; ///< Flowrate at old timestep (t_n) @@ -297,6 +300,20 @@ class CoupledBoundaryCondition { /// @brief Set the svOneD input file path void set_oned_input_file(const std::string& path); + + /// @brief Get the pressure ramp step count (0 = disabled). + int get_oned_ramp_steps() const { return oned_ramp_steps_; } + + /// @brief Set the pressure ramp parameters for 1D coupling initialization. + /// @param steps Number of time steps over which to ramp (0 = disabled). + /// @param P_ref Reference pressure at step 0 (typically the 1D initial pressure). + void set_oned_ramp(int steps, double P_ref) { + oned_ramp_steps_ = steps; + oned_ramp_ref_pressure_ = P_ref; + } + + /// @brief Get the ramp reference pressure. + double get_oned_ramp_ref_pressure() const { return oned_ramp_ref_pressure_; } /// @brief Set the svZeroD solution IDs for flow and pressure /// @param flow_id Flow solution ID diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index 1e8e6e317..a76d0c409 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -469,6 +469,8 @@ CouplingInterfaceParameters::CouplingInterfaceParameters() set_parameter("svZeroDSolver_block", "", !required, svzerod_solver_block); set_parameter("Chamber_cap_surface", "", !required, chamber_cap_surface); set_parameter("svOneDSolver_input_file","", !required, svoned_input_file); + set_parameter("Coupling_ramp_steps", 0, !required, coupling_ramp_steps); + set_parameter("Coupling_ramp_ref_pressure", 0.0, !required, coupling_ramp_ref_pressure); } void CouplingInterfaceParameters::set_values(tinyxml2::XMLElement* xml_elem) diff --git a/Code/Source/solver/Parameters.h b/Code/Source/solver/Parameters.h index 0eb060477..285a611de 100644 --- a/Code/Source/solver/Parameters.h +++ b/Code/Source/solver/Parameters.h @@ -806,6 +806,13 @@ class CouplingInterfaceParameters : public ParameterLists // Path to the svOneDSolver .in input file for this face (1D coupling). Parameter svoned_input_file; + // Pressure ramp for 1D coupling initialization (DIR coupling only). + // Over the first Coupling_ramp_steps time steps the pressure passed to the + // 1D solver is linearly ramped from Coupling_ramp_ref_pressure to the + // actual 3D pressure. Set Coupling_ramp_steps = 0 (default) to disable. + Parameter coupling_ramp_steps; + Parameter coupling_ramp_ref_pressure; + bool value_set = false; }; diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 07a4c8d29..cf88e7abf 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -462,6 +462,16 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, /*block_name=*/"", lEq.phys, /*follower_pressure_load=*/false); lBc.coupled_bc.set_oned_input_file(lBc.oned_input_file); + // Read optional pressure ramp parameters for 1D coupling initialization. + if (bc_params->coupling_interface.coupling_ramp_steps.defined() && + bc_params->coupling_interface.coupling_ramp_steps.value() > 0) { + int ramp_steps = bc_params->coupling_interface.coupling_ramp_steps.value(); + double ramp_P_ref = bc_params->coupling_interface.coupling_ramp_ref_pressure.defined() + ? bc_params->coupling_interface.coupling_ramp_ref_pressure.value() + : 0.0; + lBc.coupled_bc.set_oned_ramp(ramp_steps, ramp_P_ref); + } + } else if (com_mod.cplBC.svzerod_solver_interface.has_data) { // ------------------------------------------------------------------ // svZeroD path (existing). diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index 0dbc7bd36..5253474db 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -101,6 +101,14 @@ struct OneDModelState { // Index into eq[0].bc[] for the BC this model services. int iBc = -1; + + // Pressure ramp for 1D coupling initialization (DIR coupling only). + // Over the first ramp_steps committed time steps the pressure sent to the + // 1D solver is linearly interpolated from ramp_ref_pressure to the actual + // 3D pressure value. Zero means no ramping. + int ramp_steps = 0; + double ramp_ref_pressure = 0.0; + int step_count = 0; ///< Number of committed (BCFlag=='L') steps taken. }; // --------------------------------------------------------------------------- @@ -161,6 +169,8 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) OneDModelState st; st.iBc = iBc; st.coupling_type = (bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) ? "NEU" : "DIR"; + st.ramp_steps = bc.coupled_bc.get_oned_ramp_steps(); + st.ramp_ref_pressure = bc.coupled_bc.get_oned_ramp_ref_pressure(); oned_models.push_back(std::move(st)); } } @@ -278,8 +288,17 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) params[3] = bc.coupled_bc.get_Qo(); params[4] = bc.coupled_bc.get_Qn(); } else { - params[3] = bc.coupled_bc.get_Po(); - params[4] = bc.coupled_bc.get_Pn(); + double raw_P_old = bc.coupled_bc.get_Po(); + double raw_P_new = bc.coupled_bc.get_Pn(); + if (st.ramp_steps > 0) { + double ramp_factor = std::min(1.0, static_cast(st.step_count) / st.ramp_steps); + double P_ref = st.ramp_ref_pressure; + params[3] = P_ref + ramp_factor * (raw_P_old - P_ref); + params[4] = P_ref + ramp_factor * (raw_P_new - P_ref); + } else { + params[3] = raw_P_old; + params[4] = raw_P_new; + } } // Working copy of solution so that 'D' steps don't corrupt the @@ -334,6 +353,9 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) // Advance the simulation clock after the final iteration. if (BCFlag == 'L') { svOneDTime += com_mod.dt; + for (auto& st : oned_models) { + st.step_count++; + } } } From 9ee81895ef64065a2c20fdf29be61ba63ed8132c Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:50 -0700 Subject: [PATCH 30/51] Add under-relaxation for DIR coupling pressure passed to 1D solver Implements exponential smoothing of the pressure boundary condition sent to the 1D solver (DIR coupling only): P_sent = omega * P_target + (1 - omega) * P_prev_sent where P_target has already been through the ramp filter. - Parameters.h/cpp: add Coupling_dir_relax_factor parameter (default 1.0) - CoupledBoundaryCondition.h/cpp: add oned_relax_factor_ field, getter, setter, copy/move and distribute() broadcast - read_files.cpp: parse and apply the new parameter - svOneD_subroutines.cpp: add relax_factor / P_prev_sent_old/new to OneDModelState; apply under-relaxation after ramp; update history only on BCFlag=='L' committed steps; NEU coupling untouched --- .../solver/CoupledBoundaryCondition.cpp | 11 ++++++- Code/Source/solver/CoupledBoundaryCondition.h | 12 +++++++ Code/Source/solver/Parameters.cpp | 1 + Code/Source/solver/Parameters.h | 5 +++ Code/Source/solver/read_files.cpp | 6 ++++ Code/Source/solver/svOneD_subroutines.cpp | 31 ++++++++++++++++--- 6 files changed, 61 insertions(+), 5 deletions(-) diff --git a/Code/Source/solver/CoupledBoundaryCondition.cpp b/Code/Source/solver/CoupledBoundaryCondition.cpp index 132415a20..5e04a8533 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.cpp +++ b/Code/Source/solver/CoupledBoundaryCondition.cpp @@ -25,6 +25,7 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(const CoupledBoundaryConditio , oned_input_file_(other.oned_input_file_) , oned_ramp_steps_(other.oned_ramp_steps_) , oned_ramp_ref_pressure_(other.oned_ramp_ref_pressure_) + , oned_relax_factor_(other.oned_relax_factor_) , Qo_(other.Qo_) , Qn_(other.Qn_) , Po_(other.Po_) @@ -59,6 +60,7 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(const CoupledBound oned_input_file_ = other.oned_input_file_; oned_ramp_steps_ = other.oned_ramp_steps_; oned_ramp_ref_pressure_ = other.oned_ramp_ref_pressure_; + oned_relax_factor_ = other.oned_relax_factor_; Qo_ = other.Qo_; Qn_ = other.Qn_; Po_ = other.Po_; @@ -92,6 +94,7 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(CoupledBoundaryCondition&& ot , oned_input_file_(std::move(other.oned_input_file_)) , oned_ramp_steps_(other.oned_ramp_steps_) , oned_ramp_ref_pressure_(other.oned_ramp_ref_pressure_) + , oned_relax_factor_(other.oned_relax_factor_) , Qo_(other.Qo_) , Qn_(other.Qn_) , Po_(other.Po_) @@ -141,6 +144,7 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(CoupledBoundaryCon oned_input_file_ = std::move(other.oned_input_file_); oned_ramp_steps_ = other.oned_ramp_steps_; oned_ramp_ref_pressure_ = other.oned_ramp_ref_pressure_; + oned_relax_factor_ = other.oned_relax_factor_; Qo_ = other.Qo_; Qn_ = other.Qn_; Po_ = other.Po_; @@ -425,7 +429,12 @@ void CoupledBoundaryCondition::distribute(const ComMod& com_mod, const CmMod& cm // Distribute 1D input file path cm.bcast(cm_mod, oned_input_file_); - + + // Distribute 1D ramp and relaxation parameters + cm.bcast(cm_mod, &oned_ramp_steps_); + cm.bcast(cm_mod, &oned_ramp_ref_pressure_); + cm.bcast(cm_mod, &oned_relax_factor_); + // Distribute face name cm.bcast(cm_mod, face_name_); diff --git a/Code/Source/solver/CoupledBoundaryCondition.h b/Code/Source/solver/CoupledBoundaryCondition.h index b01d54324..a52d0375e 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.h +++ b/Code/Source/solver/CoupledBoundaryCondition.h @@ -196,6 +196,11 @@ class CoupledBoundaryCondition { /// @brief Pressure ramp parameters for 1D coupling initialization (DIR coupling only). int oned_ramp_steps_ = 0; ///< Number of ramp steps (0 = disabled) double oned_ramp_ref_pressure_ = 0.0; ///< Reference pressure at step 0 + + /// @brief Under-relaxation factor for pressure passed to the 1D solver (DIR coupling only). + /// Applied as: P_sent = omega * P_new + (1 - omega) * P_prev_sent. + /// Range: (0, 1]. Default 1.0 = no relaxation. + double oned_relax_factor_ = 1.0; /// @brief Flowrate data double Qo_ = 0.0; ///< Flowrate at old timestep (t_n) @@ -314,6 +319,13 @@ class CoupledBoundaryCondition { /// @brief Get the ramp reference pressure. double get_oned_ramp_ref_pressure() const { return oned_ramp_ref_pressure_; } + + /// @brief Get the under-relaxation factor for DIR coupling pressure (1.0 = no relaxation). + double get_oned_relax_factor() const { return oned_relax_factor_; } + + /// @brief Set the under-relaxation factor for DIR coupling pressure. + /// @param omega Relaxation factor in (0, 1]. 1.0 disables relaxation. + void set_oned_relax_factor(double omega) { oned_relax_factor_ = omega; } /// @brief Set the svZeroD solution IDs for flow and pressure /// @param flow_id Flow solution ID diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index a76d0c409..e0b9d3dc7 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -471,6 +471,7 @@ CouplingInterfaceParameters::CouplingInterfaceParameters() set_parameter("svOneDSolver_input_file","", !required, svoned_input_file); set_parameter("Coupling_ramp_steps", 0, !required, coupling_ramp_steps); set_parameter("Coupling_ramp_ref_pressure", 0.0, !required, coupling_ramp_ref_pressure); + set_parameter("Coupling_dir_relax_factor", 1.0, !required, coupling_dir_relax_factor); } void CouplingInterfaceParameters::set_values(tinyxml2::XMLElement* xml_elem) diff --git a/Code/Source/solver/Parameters.h b/Code/Source/solver/Parameters.h index 285a611de..5a8497d6e 100644 --- a/Code/Source/solver/Parameters.h +++ b/Code/Source/solver/Parameters.h @@ -813,6 +813,11 @@ class CouplingInterfaceParameters : public ParameterLists Parameter coupling_ramp_steps; Parameter coupling_ramp_ref_pressure; + // Under-relaxation factor for pressure passed to the 1D solver (DIR coupling only). + // Applied as: P_sent = omega * P_new + (1 - omega) * P_prev_sent. + // Range: (0, 1]. Default 1.0 = no relaxation. + Parameter coupling_dir_relax_factor; + bool value_set = false; }; diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index cf88e7abf..52321b3a8 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -472,6 +472,12 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, lBc.coupled_bc.set_oned_ramp(ramp_steps, ramp_P_ref); } + // Read optional under-relaxation factor for DIR coupling pressure. + if (bc_params->coupling_interface.coupling_dir_relax_factor.defined()) { + double omega = bc_params->coupling_interface.coupling_dir_relax_factor.value(); + lBc.coupled_bc.set_oned_relax_factor(omega); + } + } else if (com_mod.cplBC.svzerod_solver_interface.has_data) { // ------------------------------------------------------------------ // svZeroD path (existing). diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index 5253474db..3cc561332 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -109,6 +109,13 @@ struct OneDModelState { int ramp_steps = 0; double ramp_ref_pressure = 0.0; int step_count = 0; ///< Number of committed (BCFlag=='L') steps taken. + + // Under-relaxation for DIR coupling pressure (omega in (0, 1]). + // Applied after ramping: P_sent = omega * P_target + (1-omega) * P_prev_sent. + // Default 1.0 = no relaxation. + double relax_factor = 1.0; + double P_prev_sent_old = 0.0; ///< Under-relaxed pressure sent at params[3] (t_old) on last 'L' step. + double P_prev_sent_new = 0.0; ///< Under-relaxed pressure sent at params[4] (t_new) on last 'L' step. }; // --------------------------------------------------------------------------- @@ -171,6 +178,7 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) st.coupling_type = (bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) ? "NEU" : "DIR"; st.ramp_steps = bc.coupled_bc.get_oned_ramp_steps(); st.ramp_ref_pressure = bc.coupled_bc.get_oned_ramp_ref_pressure(); + st.relax_factor = bc.coupled_bc.get_oned_relax_factor(); oned_models.push_back(std::move(st)); } } @@ -290,15 +298,25 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) } else { double raw_P_old = bc.coupled_bc.get_Po(); double raw_P_new = bc.coupled_bc.get_Pn(); + + // Step 1: apply pressure ramp (scales amplitude from ramp_ref_pressure + // to actual 3D pressure over the first ramp_steps steps). + double P_target_old, P_target_new; if (st.ramp_steps > 0) { double ramp_factor = std::min(1.0, static_cast(st.step_count) / st.ramp_steps); double P_ref = st.ramp_ref_pressure; - params[3] = P_ref + ramp_factor * (raw_P_old - P_ref); - params[4] = P_ref + ramp_factor * (raw_P_new - P_ref); + P_target_old = P_ref + ramp_factor * (raw_P_old - P_ref); + P_target_new = P_ref + ramp_factor * (raw_P_new - P_ref); } else { - params[3] = raw_P_old; - params[4] = raw_P_new; + P_target_old = raw_P_old; + P_target_new = raw_P_new; } + + // Step 2: apply under-relaxation (damps timestep-to-timestep oscillations). + // P_sent = omega * P_target + (1 - omega) * P_prev_sent + const double omega = st.relax_factor; + params[3] = omega * P_target_old + (1.0 - omega) * st.P_prev_sent_old; + params[4] = omega * P_target_new + (1.0 - omega) * st.P_prev_sent_new; } // Working copy of solution so that 'D' steps don't corrupt the @@ -323,6 +341,11 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) // Commit the updated solution only on the final iteration. if (BCFlag == 'L') { st.solution = work_sol; + // Update the under-relaxation history with the values actually sent. + if (st.coupling_type != "NEU") { + st.P_prev_sent_old = params[3]; + st.P_prev_sent_new = params[4]; + } } } From d9657a40a6f49ee54caa8e66e973f4cfbae4e68c Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:51 -0700 Subject: [PATCH 31/51] Apply under-relaxation to Q output for 1D Dirichlet coupling --- Code/Source/solver/svOneD_subroutines.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index 3cc561332..99fff3421 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -110,12 +110,14 @@ struct OneDModelState { double ramp_ref_pressure = 0.0; int step_count = 0; ///< Number of committed (BCFlag=='L') steps taken. - // Under-relaxation for DIR coupling pressure (omega in (0, 1]). - // Applied after ramping: P_sent = omega * P_target + (1-omega) * P_prev_sent. + // Under-relaxation for DIR coupling (omega in (0, 1]). + // Applied after ramping for pressure input: P_sent = omega * P_target + (1-omega) * P_prev_sent. + // Also applied to flow rate output: Q_relaxed = omega * Q_raw + (1-omega) * Q_prev_sent. // Default 1.0 = no relaxation. double relax_factor = 1.0; double P_prev_sent_old = 0.0; ///< Under-relaxed pressure sent at params[3] (t_old) on last 'L' step. double P_prev_sent_new = 0.0; ///< Under-relaxed pressure sent at params[4] (t_new) on last 'L' step. + double Q_prev_sent = 0.0; ///< Under-relaxed flow rate output on last 'L' step (DIR coupling only). }; // --------------------------------------------------------------------------- @@ -365,8 +367,15 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) // for an inlet face (outward normal points away from the domain). // This matches the svZeroD convention: in_out = -1 for DIR (outlet of 0D // = inlet of 3D), giving QCoupled = -1 * lpn_state_y[flow_id]. + double Q_raw = -cpl_values[k]; + // Apply under-relaxation to the Q output to damp timestep-to-timestep oscillations. + const double omega = st.relax_factor; + double Qn_relaxed = omega * Q_raw + (1.0 - omega) * st.Q_prev_sent; double Qo_prev = cpl_bc.get_Qn(); - cpl_bc.set_flowrates(Qo_prev, -cpl_values[k]); + cpl_bc.set_flowrates(Qo_prev, Qn_relaxed); + if (BCFlag == 'L') { + st.Q_prev_sent = Qn_relaxed; + } } else { // 1D solver returns pressure P for NEU coupling. cpl_bc.set_pressure(cpl_values[k]); From 5566f850374abbc5c6d5f756f2931b12b714bb65 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:52 -0700 Subject: [PATCH 32/51] feat: add under-relaxation to NEU coupling pressure output --- Code/Source/solver/svOneD_subroutines.cpp | 27 ++++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index 99fff3421..bffdf27bf 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -110,14 +110,19 @@ struct OneDModelState { double ramp_ref_pressure = 0.0; int step_count = 0; ///< Number of committed (BCFlag=='L') steps taken. - // Under-relaxation for DIR coupling (omega in (0, 1]). - // Applied after ramping for pressure input: P_sent = omega * P_target + (1-omega) * P_prev_sent. - // Also applied to flow rate output: Q_relaxed = omega * Q_raw + (1-omega) * Q_prev_sent. - // Default 1.0 = no relaxation. + // Under-relaxation (omega in (0, 1]). Default 1.0 = no relaxation. + // + // DIR coupling: + // Input (P sent to 1D): P_sent = omega * P_target + (1-omega) * P_prev_sent + // Output (Q from 1D) : Q_relax = omega * Q_raw + (1-omega) * Q_prev_sent + // + // NEU coupling: + // Output (P from 1D) : P_relax = omega * P_raw + (1-omega) * P_neu_prev double relax_factor = 1.0; - double P_prev_sent_old = 0.0; ///< Under-relaxed pressure sent at params[3] (t_old) on last 'L' step. - double P_prev_sent_new = 0.0; ///< Under-relaxed pressure sent at params[4] (t_new) on last 'L' step. - double Q_prev_sent = 0.0; ///< Under-relaxed flow rate output on last 'L' step (DIR coupling only). + double P_prev_sent_old = 0.0; ///< Under-relaxed pressure sent at params[3] (t_old) on last 'L' step (DIR). + double P_prev_sent_new = 0.0; ///< Under-relaxed pressure sent at params[4] (t_new) on last 'L' step (DIR). + double Q_prev_sent = 0.0; ///< Under-relaxed flow rate output on last 'L' step (DIR only). + double P_neu_prev = 0.0; ///< Under-relaxed pressure output on last 'L' step (NEU only). }; // --------------------------------------------------------------------------- @@ -378,7 +383,13 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) } } else { // 1D solver returns pressure P for NEU coupling. - cpl_bc.set_pressure(cpl_values[k]); + // Apply under-relaxation to damp timestep-to-timestep oscillations. + const double omega = st.relax_factor; + double P_relaxed = omega * cpl_values[k] + (1.0 - omega) * st.P_neu_prev; + cpl_bc.set_pressure(P_relaxed); + if (BCFlag == 'L') { + st.P_neu_prev = P_relaxed; + } } } From 097bbd36f6a6e64ba52399ef7e1edcf1d62f2789 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:53 -0700 Subject: [PATCH 33/51] remove -dir at relax_factor --- Code/Source/solver/Parameters.cpp | 2 +- Code/Source/solver/Parameters.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index e0b9d3dc7..6ae85489c 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -471,7 +471,7 @@ CouplingInterfaceParameters::CouplingInterfaceParameters() set_parameter("svOneDSolver_input_file","", !required, svoned_input_file); set_parameter("Coupling_ramp_steps", 0, !required, coupling_ramp_steps); set_parameter("Coupling_ramp_ref_pressure", 0.0, !required, coupling_ramp_ref_pressure); - set_parameter("Coupling_dir_relax_factor", 1.0, !required, coupling_dir_relax_factor); + set_parameter("Coupling_relax_factor", 1.0, !required, coupling_relax_factor); } void CouplingInterfaceParameters::set_values(tinyxml2::XMLElement* xml_elem) diff --git a/Code/Source/solver/Parameters.h b/Code/Source/solver/Parameters.h index 5a8497d6e..2c042ed6d 100644 --- a/Code/Source/solver/Parameters.h +++ b/Code/Source/solver/Parameters.h @@ -816,7 +816,7 @@ class CouplingInterfaceParameters : public ParameterLists // Under-relaxation factor for pressure passed to the 1D solver (DIR coupling only). // Applied as: P_sent = omega * P_new + (1 - omega) * P_prev_sent. // Range: (0, 1]. Default 1.0 = no relaxation. - Parameter coupling_dir_relax_factor; + Parameter coupling_relax_factor; bool value_set = false; }; From c727c57c4b7522520e4110511835b43eafff52fd Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:54 -0700 Subject: [PATCH 34/51] error at the previous commit. this is correct version after remove -dir in relax_factor --- Code/Source/solver/read_files.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 52321b3a8..0fba1dfd9 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -473,8 +473,8 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, } // Read optional under-relaxation factor for DIR coupling pressure. - if (bc_params->coupling_interface.coupling_dir_relax_factor.defined()) { - double omega = bc_params->coupling_interface.coupling_dir_relax_factor.value(); + if (bc_params->coupling_interface.coupling_relax_factor.defined()) { + double omega = bc_params->coupling_interface.coupling_relax_factor.value(); lBc.coupled_bc.set_oned_relax_factor(omega); } From 4f8601aec23fa5756fc83fc0df55dcdc054e183b Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 17 Apr 2026 15:16:55 -0700 Subject: [PATCH 35/51] =?UTF-8?q?Rebase=20onto=20collaborator=20cleanup=20?= =?UTF-8?q?(6a2a54b):=20svZeroD=5Fsubroutines=E2=86=92svZeroD=5Finterface,?= =?UTF-8?q?=20delete=20legacy=20sv0D=20code,=20keep=20all=20svOneD=203D-1D?= =?UTF-8?q?=20coupling=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solver/CoupledBoundaryCondition.cpp | 68 ++++++- Code/Source/solver/CoupledBoundaryCondition.h | 18 ++ Code/Source/solver/Parameters.cpp | 182 ++++++++++++++++++ Code/Source/solver/Parameters.h | 1 + Code/Source/solver/read_files.cpp | 3 + Code/Source/solver/set_bc.cpp | 3 +- 6 files changed, 267 insertions(+), 8 deletions(-) diff --git a/Code/Source/solver/CoupledBoundaryCondition.cpp b/Code/Source/solver/CoupledBoundaryCondition.cpp index 5e04a8533..d25249937 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.cpp +++ b/Code/Source/solver/CoupledBoundaryCondition.cpp @@ -16,6 +16,8 @@ // CoupledBoundaryCondition // ========================================================================= +const CmMod CoupledBoundaryCondition::cm_mod_{}; + CoupledBoundaryCondition::CoupledBoundaryCondition(const CoupledBoundaryCondition& other) : face_(other.face_) , cap_face_vtp_file_(other.cap_face_vtp_file_) @@ -909,8 +911,27 @@ CappingSurface::CappingSurface(const CappingSurface& other) , valM_(other.valM_) , normals_(other.normals_) { - if (other.face_) { - face_ = std::make_unique(*other.face_); + if (other.face_ != nullptr) { + try { + face_ = std::make_unique(*other.face_); + } catch (const std::exception& e) { + throw CappingSurfaceCopyException("[CappingSurface::copy constructor] Failed to copy face_: " + + std::string(e.what())); + } + if (face_ != nullptr) { + if (face_->nNo > 0 && face_->gN.size() != face_->nNo) { + throw CappingSurfaceCopyException("[CappingSurface::copy constructor] Invalid face_: gN.size()=" + + std::to_string(face_->gN.size()) + " != nNo=" + + std::to_string(face_->nNo)); + } + if (face_->nEl > 0 && face_->IEN.ncols() != face_->nEl) { + throw CappingSurfaceCopyException("[CappingSurface::copy constructor] Invalid face_: IEN.ncols()=" + + std::to_string(face_->IEN.ncols()) + " != nEl=" + + std::to_string(face_->nEl)); + } + } + } else { + face_.reset(); } } @@ -920,8 +941,13 @@ CappingSurface& CappingSurface::operator=(const CappingSurface& other) global_node_ids_ = other.global_node_ids_; valM_ = other.valM_; normals_ = other.normals_; - if (other.face_) { - face_ = std::make_unique(*other.face_); + if (other.face_ != nullptr) { + try { + face_ = std::make_unique(*other.face_); + } catch (const std::exception& e) { + throw CappingSurfaceCopyException("[CappingSurface::operator=] Failed to copy face_: " + + std::string(e.what())); + } } else { face_.reset(); } @@ -1020,7 +1046,7 @@ void CappingSurface::init_cap_face_quadrature(const ComMod& com_mod) try { if (nsd != cap_nsd_) { - throw CappingSurfaceBaseException("[CappingSurface::init_cap_face_quadrature] Cap surface requires nsd=3."); + throw CappingSurfaceGeometryException("[CappingSurface::init_cap_face_quadrature] Cap surface requires nsd=3."); } face_->nG = 1; @@ -1068,6 +1094,10 @@ Array CappingSurface::update_element_position_global(int e, consts::Mech { using namespace consts; + if (mesh_x.nrows() < cap_nsd_ || mesh_Do.nrows() < cap_nsd_ || mesh_Dn.nrows() < cap_nsd_) { + throw CappingSurfaceGeometryException( + "[CappingSurface::update_element_position_global] Mesh arrays must have at least 3 rows."); + } Array xl(cap_nsd_, face_->eNoN); for (int a = 0; a < face_->eNoN; a++) { @@ -1097,6 +1127,13 @@ Array CappingSurface::update_element_position_global(int e, consts::Mech /// @return The Jacobian and normal vector. std::pair> CappingSurface::compute_jacobian_and_normal(const Array& xl, int e, int g) const { + if (xl.nrows() != cap_nsd_ || xl.ncols() != face_->eNoN) { + throw CappingSurfaceGeometryException("[CappingSurface::compute_jacobian_and_normal] xl has wrong dimensions: " + + std::to_string(xl.nrows()) + "x" + std::to_string(xl.ncols()) + + " (expected " + std::to_string(cap_nsd_) + "x" + + std::to_string(face_->eNoN) + ")."); + } + // Get the shape function derivatives for the Gauss point. Array Nx_g = face_->Nx.rslice(g); Array xXi(cap_nsd_, cap_insd_); @@ -1118,7 +1155,7 @@ std::pair> CappingSurface::compute_jacobian_and_normal(co Jac = sqrt(utils::norm(n)); if (utils::is_zero(Jac)) { - throw CappingSurfaceBaseException("[CappingSurface::compute_jacobian_and_normal] Zero Jacobian at Gauss point " + + throw CappingSurfaceGeometryException("[CappingSurface::compute_jacobian_and_normal] Zero Jacobian at Gauss point " + std::to_string(g)); } @@ -1126,6 +1163,11 @@ std::pair> CappingSurface::compute_jacobian_and_normal(co // Check if the initial normals are provided and if they are valid. if (normals_.ncols() > 0 && normals_.nrows() == cap_nsd_) { + if (e < 0 || e >= normals_.ncols()) { + throw CappingSurfaceGeometryException("[CappingSurface::compute_jacobian_and_normal] Element index e=" + + std::to_string(e) + " is out of bounds for normals_ (ncols=" + + std::to_string(normals_.ncols()) + ")."); + } Vector n0(cap_nsd_); for (int i = 0; i < cap_nsd_; i++) { @@ -1209,7 +1251,19 @@ void CappingSurface::compute_valM(consts::MechanicalConfigurationType cfg, const auto [Jac, n] = compute_jacobian_and_normal(xl, e, g); for (int a = 0; a < face_->eNoN; a++) { int gnNo_idx = face_->IEN(a, e); - int cap_a = gnNo_to_cap_local.at(gnNo_idx); + auto it = gnNo_to_cap_local.find(gnNo_idx); + if (it == gnNo_to_cap_local.end()) { + throw CappingSurfaceAssemblyException("[CappingSurface::compute_valM] IEN entry (element " + + std::to_string(e) + ", node " + std::to_string(a) + + ") contains invalid gnNo index " + std::to_string(gnNo_idx) + + " not found in cap face nodes."); + } + int cap_a = it->second; + if (cap_a < 0 || cap_a >= cap_nNo) { + throw CappingSurfaceAssemblyException("[CappingSurface::compute_valM] Invalid cap face-local index cap_a=" + + std::to_string(cap_a) + " (cap_nNo=" + std::to_string(cap_nNo) + + ")"); + } for (int i = 0; i < cap_nsd_; i++) { valM_(i, cap_a) += face_->N(a, g) * face_->w(g) * Jac * n(i); } diff --git a/Code/Source/solver/CoupledBoundaryCondition.h b/Code/Source/solver/CoupledBoundaryCondition.h index a52d0375e..2ba180da6 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.h +++ b/Code/Source/solver/CoupledBoundaryCondition.h @@ -98,6 +98,12 @@ class CappingSurfaceInvalidElementNodesException : public CappingSurfaceBaseExce std::to_string(eNoN) + " (expected " + std::to_string(expected) + ").") {} }; +/// @brief Geometry or Jacobian error during cap surface integration. +class CappingSurfaceGeometryException : public CappingSurfaceBaseException { +public: + explicit CappingSurfaceGeometryException(const std::string& detail) : CappingSurfaceBaseException(detail) {} +}; + /// @brief Cap face quadrature (shape functions on TRI3) setup failed. class CappingSurfaceQuadratureException : public CappingSurfaceBaseException { public: @@ -106,6 +112,18 @@ class CappingSurfaceQuadratureException : public CappingSurfaceBaseException { "functions: " + nested) {} }; +/// @brief Inconsistent cap connectivity or assembly indexing. +class CappingSurfaceAssemblyException : public CappingSurfaceBaseException { +public: + explicit CappingSurfaceAssemblyException(const std::string& detail) : CappingSurfaceBaseException(detail) {} +}; + +/// @brief Failure copying a \ref CappingSurface or its internal face. +class CappingSurfaceCopyException : public CappingSurfaceBaseException { +public: + explicit CappingSurfaceCopyException(std::string msg) : CappingSurfaceBaseException(std::move(msg)) {} +}; + /// @brief Capping surface geometry and integration for a coupled boundary. class CappingSurface { public: diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index 6ae85489c..2434d97c3 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -1858,6 +1858,7 @@ void DomainParameters::print_parameters() fluid_viscosity.print_parameters(); solid_viscosity.print_parameters(); + } //------------ @@ -1975,6 +1976,187 @@ void DomainParameters::set_values(tinyxml2::XMLElement* domain_elem, bool from_e */ } +////////////////////////////////////////////////////////// +// TTPInitialConditionsParameters // +////////////////////////////////////////////////////////// + +const std::string TTPInitialConditionsParameters::xml_element_name_ = "TTP_initial_conditions"; + +TTPInitialConditionsParameters::TTPInitialConditionsParameters() +{ +} + +void TTPInitialConditionsParameters::print_parameters() +{ + if (value_set) { + std::cout << std::endl; + std::cout << "TTP Initial Conditions Parameters" << std::endl; + std::cout << "---------------------------------" << std::endl; + initial_states.print_parameters(); + gating_variables.print_parameters(); + } +} + +void TTPInitialConditionsParameters::set_values(tinyxml2::XMLElement* xml_elem) +{ + using namespace tinyxml2; + std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; + + auto item = xml_elem->FirstChildElement(); + + while (item != nullptr) { + auto name = std::string(item->Value()); + + if (name == TTPInitialStatesParameters::xml_element_name_) { + initial_states.set_values(item); + value_set = true; + + } else if (name == TTPGatingVariablesParameters::xml_element_name_) { + gating_variables.set_values(item); + value_set = true; + + } else { + throw std::runtime_error(error_msg + name + "'."); + } + + item = item->NextSiblingElement(); + } + + if (!initial_states.defined()) { + throw std::runtime_error(xml_element_name_ + " requires an '" + + TTPInitialStatesParameters::xml_element_name_ + "' XML section."); + } + + if (!gating_variables.defined()) { + throw std::runtime_error(xml_element_name_ + " requires a '" + + TTPGatingVariablesParameters::xml_element_name_ + "' XML section."); + } +} + +////////////////////////////////////////////////////////// +// TTPInitialStatesParameters // +////////////////////////////////////////////////////////// + +const std::string TTPInitialStatesParameters::xml_element_name_ = "Initial_states"; + +TTPInitialStatesParameters::TTPInitialStatesParameters() +{ + bool required = true; + + set_parameter("V", -85.23, required, V); + set_parameter("K_i", 136.89, required, K_i); + set_parameter("Na_i", 8.6040, required, Na_i); + set_parameter("Ca_i", 1.26E-4, required, Ca_i); + set_parameter("Ca_ss", 3.6E-4, required, Ca_ss); + set_parameter("Ca_sr", 3.64, required, Ca_sr); + set_parameter("R_bar", 0.9073, required, R_bar); +} + +void TTPInitialStatesParameters::print_parameters() +{ + if (value_set) { + std::cout << " Initial States:" << std::endl; + auto params_name_value = get_parameter_list(); + for (auto& [key, value] : params_name_value) { + std::cout << " " << key << ": " << value << std::endl; + } + } +} + +void TTPInitialStatesParameters::set_values(tinyxml2::XMLElement* xml_elem) +{ + using namespace tinyxml2; + std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; + + auto item = xml_elem->FirstChildElement(); + + while (item != nullptr) { + auto name = std::string(item->Value()); + + if (item->GetText() != nullptr) { + auto value = item->GetText(); + try { + set_parameter_value(name, value); + value_set = true; + } catch (const std::bad_function_call& exception) { + throw std::runtime_error(error_msg + name + "'."); + } + } else { + throw std::runtime_error(error_msg + name + "'."); + } + + item = item->NextSiblingElement(); + } + + check_required(); +} + +////////////////////////////////////////////////////////// +// TTPGatingVariablesParameters // +////////////////////////////////////////////////////////// + +const std::string TTPGatingVariablesParameters::xml_element_name_ = "Gating_variables"; + +TTPGatingVariablesParameters::TTPGatingVariablesParameters() +{ + bool required = true; + + set_parameter("x_r1_rectifier", 6.21E-3, required, x_r1_rectifier); + set_parameter("x_r2_rectifier", 0.4712, required, x_r2_rectifier); + set_parameter("x_s_rectifier", 9.5E-3, required, x_s_rectifier); + + set_parameter("m_fast_Na", 1.72E-3, required, m_fast_Na); + set_parameter("h_fast_Na", 0.7444, required, h_fast_Na); + set_parameter("j_fast_Na", 0.7045, required, j_fast_Na); + + set_parameter("d_slow_in", 3.373E-5, required, d_slow_in); + set_parameter("f_slow_in", 0.7888, required, f_slow_in); + set_parameter("f2_slow_in", 0.9755, required, f2_slow_in); + set_parameter("fcass_slow_in", 0.9953, required, fcass_slow_in); + + set_parameter("s_out", 0.999998, required, s_out); + set_parameter("r_out", 2.42E-8, required, r_out); +} + +void TTPGatingVariablesParameters::print_parameters() +{ + if (value_set) { + std::cout << " Gating Variables:" << std::endl; + auto params_name_value = get_parameter_list(); + for (auto& [key, value] : params_name_value) { + std::cout << " " << key << ": " << value << std::endl; + } + } +} + +void TTPGatingVariablesParameters::set_values(tinyxml2::XMLElement* xml_elem) +{ + using namespace tinyxml2; + std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; + + auto item = xml_elem->FirstChildElement(); + + while (item != nullptr) { + auto name = std::string(item->Value()); + + if (item->GetText() != nullptr) { + auto value = item->GetText(); + try { + set_parameter_value(name, value); + value_set = true; + } catch (const std::bad_function_call& exception) { + throw std::runtime_error(error_msg + name + "'."); + } + } else { + throw std::runtime_error(error_msg + name + "'."); + } + + item = item->NextSiblingElement(); + } + + check_required(); +} + ////////////////////////////////////////////////////////// // DirectionalDistributionParameters // ////////////////////////////////////////////////////////// diff --git a/Code/Source/solver/Parameters.h b/Code/Source/solver/Parameters.h index 2c042ed6d..3f2e45b1a 100644 --- a/Code/Source/solver/Parameters.h +++ b/Code/Source/solver/Parameters.h @@ -1402,6 +1402,7 @@ class DomainParameters : public ParameterLists StimulusParameters stimulus; FluidViscosityParameters fluid_viscosity; SolidViscosityParameters solid_viscosity; + TTPInitialConditionsParameters ttp_initial_conditions; // Ionic model parameters. std::map> ionic_models; diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 0fba1dfd9..0bb120e97 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -302,6 +302,8 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, " is defined on the equation."); } + + lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_cpl)); com_mod.cplBC.nFa = com_mod.cplBC.nFa + 1; lBc.cplBCptr = com_mod.cplBC.nFa - 1; @@ -539,6 +541,7 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, com_mod.msh[lBc.iM].fa[lBc.iFa].name, zd_block, lEq.phys, cpl_flwP); } + } else { throw std::runtime_error( std::string("[read_bc] bType_Coupled is set on face '") + face_name + diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index 03f364831..363fa803d 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -216,7 +216,7 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& diff = diff*relTol; } - // Store the original pressures and flowrates + // Store the original pressures and flowrates for cplBC std::vector orgY(cplBC.fa.size()); std::vector orgQ(cplBC.fa.size()); @@ -313,6 +313,7 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& } /// @brief RCR (Windkessel) integration for the non-genBC / non-svZeroD branch. +/// The legacy external Fortran Couple_to_cplBC file coupling has been removed. void cplBC_Integ_X(ComMod& com_mod, const CmMod& cm_mod, const bool RCRflag) { using namespace consts; From c1861a8b152dcb260970edf57884340028ce199f Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Mon, 20 Apr 2026 16:38:11 -0700 Subject: [PATCH 36/51] Fix const-correctness in CappingSurface: face() const, mutable valM_, compute_valM/compute_jacobian_and_normal const --- Code/Source/solver/CoupledBoundaryCondition.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Code/Source/solver/CoupledBoundaryCondition.h b/Code/Source/solver/CoupledBoundaryCondition.h index 2ba180da6..de1b4f424 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.h +++ b/Code/Source/solver/CoupledBoundaryCondition.h @@ -151,12 +151,11 @@ class CappingSurface { /// Surface velocity flux through the cap using \a st columns indexed by cap IEN / GlobalNodeID (master / serial). double integrate_velocity_flux(const CapGlobalMeshState& st, bool use_Yn_velocity, consts::MechanicalConfigurationType cfg); - - /// @brief Compute the cap contribution to the linear solver face (fills \ref valM_; safe under \c const *this). + + /// @brief Compute the cap contribution to the linear solver face (fills \ref valM_; safe under \c const *this). void compute_valM(consts::MechanicalConfigurationType cfg, const CapGlobalMeshState& st) const; /// @brief Get the cap face. - faceType* face() { return face_.get(); } const faceType* face() const { return face_.get(); } /// @brief Get the cap contribution. From cd0d072e5a45a0c3904e870965c443cfdae7d1eb Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Mon, 20 Apr 2026 16:38:14 -0700 Subject: [PATCH 37/51] fix: broadcast DIR flowrate Qo/Qn to all MPI ranks in svZeroD coupling --- .../solver/CoupledBoundaryCondition.cpp | 20 +++++++++++++++++++ Code/Source/solver/CoupledBoundaryCondition.h | 3 +++ Code/Source/solver/svZeroD_interface.cpp | 3 +++ 3 files changed, 26 insertions(+) diff --git a/Code/Source/solver/CoupledBoundaryCondition.cpp b/Code/Source/solver/CoupledBoundaryCondition.cpp index d25249937..63c46cffa 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.cpp +++ b/Code/Source/solver/CoupledBoundaryCondition.cpp @@ -1399,3 +1399,23 @@ void CoupledBoundaryCondition::bcast_coupled_neumann_pressure(const CmMod& cm_mo cm.bcast(cm_mod, &pr); set_pressure(pr); } + +void CoupledBoundaryCondition::bcast_coupled_dir_flowrate(const CmMod& cm_mod, cmType& cm) +{ + if (cm.seq()) { + return; + } + using namespace consts; + if (get_bc_type() != BoundaryConditionType::bType_Dir) { + return; + } + double Qo = 0.0; + double Qn = 0.0; + if (cm.mas(cm_mod)) { + Qo = get_Qo(); + Qn = get_Qn(); + } + cm.bcast(cm_mod, &Qo); + cm.bcast(cm_mod, &Qn); + set_flowrates(Qo, Qn); +} diff --git a/Code/Source/solver/CoupledBoundaryCondition.h b/Code/Source/solver/CoupledBoundaryCondition.h index de1b4f424..c32cf220c 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.h +++ b/Code/Source/solver/CoupledBoundaryCondition.h @@ -469,6 +469,9 @@ class CoupledBoundaryCondition { /// @brief Master reads Neumann pressure, one scalar \c MPI_Bcast, all ranks set pressure (svZeroD sync). void bcast_coupled_neumann_pressure(const CmMod& cm_mod, cmType& cm); + /// @brief Master reads DIR flowrates (Qo, Qn), two scalar \c MPI_Bcast, all ranks set flowrates (svZeroD sync). + void bcast_coupled_dir_flowrate(const CmMod& cm_mod, cmType& cm); + }; #endif // COUPLED_BOUNDARY_CONDITION_H diff --git a/Code/Source/solver/svZeroD_interface.cpp b/Code/Source/solver/svZeroD_interface.cpp index 470f5fff8..1d6293304 100644 --- a/Code/Source/solver/svZeroD_interface.cpp +++ b/Code/Source/solver/svZeroD_interface.cpp @@ -486,6 +486,9 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) if (utils::btest(bc.bType, iBC_Coupled) && bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) { bc.coupled_bc.bcast_coupled_neumann_pressure(cm_mod, cm); + } else if (utils::btest(bc.bType, iBC_Coupled) && + bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Dir) { + bc.coupled_bc.bcast_coupled_dir_flowrate(cm_mod, cm); } } } From a6e0c22e8b75d491cf4e51ffb06dd2bf4534c693 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Mon, 20 Apr 2026 16:38:15 -0700 Subject: [PATCH 38/51] feat: add ramp/relax support to 3D-0D coupling (svZeroD) --- .../solver/CoupledBoundaryCondition.cpp | 20 ++++++ Code/Source/solver/CoupledBoundaryCondition.h | 46 +++++++++++++- Code/Source/solver/read_files.cpp | 16 +++++ Code/Source/solver/svZeroD_interface.cpp | 61 +++++++++++++++++-- 4 files changed, 135 insertions(+), 8 deletions(-) diff --git a/Code/Source/solver/CoupledBoundaryCondition.cpp b/Code/Source/solver/CoupledBoundaryCondition.cpp index 63c46cffa..00c6e7150 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.cpp +++ b/Code/Source/solver/CoupledBoundaryCondition.cpp @@ -28,6 +28,11 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(const CoupledBoundaryConditio , oned_ramp_steps_(other.oned_ramp_steps_) , oned_ramp_ref_pressure_(other.oned_ramp_ref_pressure_) , oned_relax_factor_(other.oned_relax_factor_) + , ramp_step_count_(other.ramp_step_count_) + , P_prev_sent_old_(other.P_prev_sent_old_) + , P_prev_sent_new_(other.P_prev_sent_new_) + , Q_prev_sent_(other.Q_prev_sent_) + , P_neu_prev_(other.P_neu_prev_) , Qo_(other.Qo_) , Qn_(other.Qn_) , Po_(other.Po_) @@ -63,6 +68,11 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(const CoupledBound oned_ramp_steps_ = other.oned_ramp_steps_; oned_ramp_ref_pressure_ = other.oned_ramp_ref_pressure_; oned_relax_factor_ = other.oned_relax_factor_; + ramp_step_count_ = other.ramp_step_count_; + P_prev_sent_old_ = other.P_prev_sent_old_; + P_prev_sent_new_ = other.P_prev_sent_new_; + Q_prev_sent_ = other.Q_prev_sent_; + P_neu_prev_ = other.P_neu_prev_; Qo_ = other.Qo_; Qn_ = other.Qn_; Po_ = other.Po_; @@ -97,6 +107,11 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(CoupledBoundaryCondition&& ot , oned_ramp_steps_(other.oned_ramp_steps_) , oned_ramp_ref_pressure_(other.oned_ramp_ref_pressure_) , oned_relax_factor_(other.oned_relax_factor_) + , ramp_step_count_(other.ramp_step_count_) + , P_prev_sent_old_(other.P_prev_sent_old_) + , P_prev_sent_new_(other.P_prev_sent_new_) + , Q_prev_sent_(other.Q_prev_sent_) + , P_neu_prev_(other.P_neu_prev_) , Qo_(other.Qo_) , Qn_(other.Qn_) , Po_(other.Po_) @@ -147,6 +162,11 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(CoupledBoundaryCon oned_ramp_steps_ = other.oned_ramp_steps_; oned_ramp_ref_pressure_ = other.oned_ramp_ref_pressure_; oned_relax_factor_ = other.oned_relax_factor_; + ramp_step_count_ = other.ramp_step_count_; + P_prev_sent_old_ = other.P_prev_sent_old_; + P_prev_sent_new_ = other.P_prev_sent_new_; + Q_prev_sent_ = other.Q_prev_sent_; + P_neu_prev_ = other.P_neu_prev_; Qo_ = other.Qo_; Qn_ = other.Qn_; Po_ = other.Po_; diff --git a/Code/Source/solver/CoupledBoundaryCondition.h b/Code/Source/solver/CoupledBoundaryCondition.h index c32cf220c..9fcad5179 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.h +++ b/Code/Source/solver/CoupledBoundaryCondition.h @@ -218,7 +218,15 @@ class CoupledBoundaryCondition { /// Applied as: P_sent = omega * P_new + (1 - omega) * P_prev_sent. /// Range: (0, 1]. Default 1.0 = no relaxation. double oned_relax_factor_ = 1.0; - + + /// @brief Ramp/relax runtime state (shared by both 0D and 1D coupling). + /// Updated only on committed ('L') time steps. + int ramp_step_count_ = 0; ///< Number of committed steps taken (used for ramp fraction). + double P_prev_sent_old_ = 0.0; ///< Under-relaxed pressure sent at t_old on last 'L' step (DIR input). + double P_prev_sent_new_ = 0.0; ///< Under-relaxed pressure sent at t_new on last 'L' step (DIR input). + double Q_prev_sent_ = 0.0; ///< Under-relaxed flow output on last 'L' step (DIR output). + double P_neu_prev_ = 0.0; ///< Under-relaxed pressure output on last 'L' step (NEU output). + /// @brief Flowrate data double Qo_ = 0.0; ///< Flowrate at old timestep (t_n) double Qn_ = 0.0; ///< Flowrate at new timestep (t_{n+1}) @@ -343,7 +351,41 @@ class CoupledBoundaryCondition { /// @brief Set the under-relaxation factor for DIR coupling pressure. /// @param omega Relaxation factor in (0, 1]. 1.0 disables relaxation. void set_oned_relax_factor(double omega) { oned_relax_factor_ = omega; } - + + // ========================================================================= + // Ramp/relax runtime state accessors (shared by 0D and 1D coupling) + // ========================================================================= + + /// @brief Get the number of committed time steps (used for ramp fraction). + int get_ramp_step_count() const { return ramp_step_count_; } + + /// @brief Increment the committed step counter (call once per 'L' step). + void increment_ramp_step_count() { ramp_step_count_++; } + + /// @brief Get the under-relaxed pressure sent at t_old on the last 'L' step (DIR input). + double get_P_prev_sent_old() const { return P_prev_sent_old_; } + + /// @brief Get the under-relaxed pressure sent at t_new on the last 'L' step (DIR input). + double get_P_prev_sent_new() const { return P_prev_sent_new_; } + + /// @brief Set the DIR input pressure history (call on 'L' steps). + void set_P_prev_sent(double old_val, double new_val) { + P_prev_sent_old_ = old_val; + P_prev_sent_new_ = new_val; + } + + /// @brief Get the under-relaxed flow output on the last 'L' step (DIR output). + double get_Q_prev_sent() const { return Q_prev_sent_; } + + /// @brief Set the DIR output flow history (call on 'L' steps). + void set_Q_prev_sent(double Q) { Q_prev_sent_ = Q; } + + /// @brief Get the under-relaxed pressure output on the last 'L' step (NEU output). + double get_P_neu_prev() const { return P_neu_prev_; } + + /// @brief Set the NEU output pressure history (call on 'L' steps). + void set_P_neu_prev(double P) { P_neu_prev_ = P; } + /// @brief Set the svZeroD solution IDs for flow and pressure /// @param flow_id Flow solution ID /// @param pressure_id Pressure solution ID diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 0bb120e97..610652800 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -542,6 +542,22 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, cpl_flwP); } + // Read optional pressure ramp parameters for 0D coupling initialization. + if (bc_params->coupling_interface.coupling_ramp_steps.defined() && + bc_params->coupling_interface.coupling_ramp_steps.value() > 0) { + int ramp_steps = bc_params->coupling_interface.coupling_ramp_steps.value(); + double ramp_P_ref = bc_params->coupling_interface.coupling_ramp_ref_pressure.defined() + ? bc_params->coupling_interface.coupling_ramp_ref_pressure.value() + : 0.0; + lBc.coupled_bc.set_oned_ramp(ramp_steps, ramp_P_ref); + } + + // Read optional under-relaxation factor for 0D coupling. + if (bc_params->coupling_interface.coupling_relax_factor.defined()) { + double omega = bc_params->coupling_interface.coupling_relax_factor.value(); + lBc.coupled_bc.set_oned_relax_factor(omega); + } + } else { throw std::runtime_error( std::string("[read_bc] bType_Coupled is set on face '") + face_name + diff --git a/Code/Source/solver/svZeroD_interface.cpp b/Code/Source/solver/svZeroD_interface.cpp index 1d6293304..20e07741e 100644 --- a/Code/Source/solver/svZeroD_interface.cpp +++ b/Code/Source/solver/svZeroD_interface.cpp @@ -386,6 +386,12 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) double params[2]; double times[2]; int error_code; + + // Temporary arrays to record relaxed values for history update on 'L' steps. + std::vector P_sent_old_arr(numCoupledSrfs, 0.0); + std::vector P_sent_new_arr(numCoupledSrfs, 0.0); + std::vector Q_relaxed_arr(numCoupledSrfs, 0.0); + std::vector P_relaxed_arr(numCoupledSrfs, 0.0); get_coupled_QP(com_mod, QCoupled, QnCoupled, PCoupled, PnCoupled); @@ -416,8 +422,30 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) double sign = bc->coupled_bc.get_in_out_sign(); if (is_dirichlet) { - params[0] = PCoupled[i]; - params[1] = PnCoupled[i]; + double raw_P_old = PCoupled[i]; + double raw_P_new = PnCoupled[i]; + + // Step 1: apply pressure ramp (scales amplitude from ramp_ref_pressure + // to actual 3D pressure over the first ramp_steps steps). + int ramp_steps = bc->coupled_bc.get_oned_ramp_steps(); + double P_target_old, P_target_new; + if (ramp_steps > 0) { + double ramp_factor = std::min(1.0, static_cast(bc->coupled_bc.get_ramp_step_count()) / ramp_steps); + double P_ref = bc->coupled_bc.get_oned_ramp_ref_pressure(); + P_target_old = P_ref + ramp_factor * (raw_P_old - P_ref); + P_target_new = P_ref + ramp_factor * (raw_P_new - P_ref); + } else { + P_target_old = raw_P_old; + P_target_new = raw_P_new; + } + + // Step 2: apply under-relaxation (damps timestep-to-timestep oscillations). + // P_sent = omega * P_target + (1 - omega) * P_prev_sent + const double omega = bc->coupled_bc.get_oned_relax_factor(); + params[0] = omega * P_target_old + (1.0 - omega) * bc->coupled_bc.get_P_prev_sent_old(); + params[1] = omega * P_target_new + (1.0 - omega) * bc->coupled_bc.get_P_prev_sent_new(); + P_sent_old_arr[i] = params[0]; + P_sent_new_arr[i] = params[1]; } else { params[0] = sign * QCoupled[i]; params[1] = sign * QnCoupled[i]; @@ -447,12 +475,20 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) } if (bc->coupled_bc.get_bc_type() == consts::BoundaryConditionType::bType_Neu) { - PCoupled[i] = lpn_state_y[pressure_id]; - bc->coupled_bc.set_pressure(PCoupled[i]); + double P_raw = lpn_state_y[pressure_id]; + // Apply under-relaxation to the pressure output. + const double omega = bc->coupled_bc.get_oned_relax_factor(); + double P_relaxed = omega * P_raw + (1.0 - omega) * bc->coupled_bc.get_P_neu_prev(); + bc->coupled_bc.set_pressure(P_relaxed); + P_relaxed_arr[i] = P_relaxed; } else if (bc->coupled_bc.get_bc_type() == consts::BoundaryConditionType::bType_Dir) { - QCoupled[i] = in_out * lpn_state_y[flow_id]; + double Q_raw = in_out * lpn_state_y[flow_id]; + // Apply under-relaxation to the flow output to damp timestep-to-timestep oscillations. + const double omega = bc->coupled_bc.get_oned_relax_factor(); + double Q_relaxed = omega * Q_raw + (1.0 - omega) * bc->coupled_bc.get_Q_prev_sent(); double Qo_prev = bc->coupled_bc.get_Qn(); - bc->coupled_bc.set_flowrates(Qo_prev, QCoupled[i]); + bc->coupled_bc.set_flowrates(Qo_prev, Q_relaxed); + Q_relaxed_arr[i] = Q_relaxed; } else { throw std::runtime_error("ERROR: [calc_svZeroD] Invalid Coupled BC type."); } @@ -463,6 +499,19 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) interface->return_ydot(last_state_ydot); std::copy(lpn_state_y.begin(), lpn_state_y.end(), last_state_y.begin()); + // Update ramp/relax history for all coupled BCs. + for (int i = 0; i < numCoupledSrfs; ++i) { + bcType* bc_hist = nullptr; + if (!nth_coupled_bc(com_mod, i, &bc_hist)) continue; + if (bc_hist->coupled_bc.get_bc_type() == consts::BoundaryConditionType::bType_Dir) { + bc_hist->coupled_bc.set_P_prev_sent(P_sent_old_arr[i], P_sent_new_arr[i]); + bc_hist->coupled_bc.set_Q_prev_sent(Q_relaxed_arr[i]); + } else { + bc_hist->coupled_bc.set_P_neu_prev(P_relaxed_arr[i]); + } + bc_hist->coupled_bc.increment_ramp_step_count(); + } + if (writeSvZeroD == 1) { // Write the state vector to a file int arg = 1; From df85a87ed82cf6e94961efc1381c888f0812ad3f Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Mon, 20 Apr 2026 16:38:16 -0700 Subject: [PATCH 39/51] feat: add ramp/relax to NEU input (Q sent to 0D solver) Co-authored-by: taeoukkim <74677557+taeoukkim@users.noreply.github.com> --- .../solver/CoupledBoundaryCondition.cpp | 8 ++++++ Code/Source/solver/CoupledBoundaryCondition.h | 14 ++++++++++ Code/Source/solver/svZeroD_interface.cpp | 27 +++++++++++++++++-- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Code/Source/solver/CoupledBoundaryCondition.cpp b/Code/Source/solver/CoupledBoundaryCondition.cpp index 00c6e7150..dabd324ca 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.cpp +++ b/Code/Source/solver/CoupledBoundaryCondition.cpp @@ -33,6 +33,8 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(const CoupledBoundaryConditio , P_prev_sent_new_(other.P_prev_sent_new_) , Q_prev_sent_(other.Q_prev_sent_) , P_neu_prev_(other.P_neu_prev_) + , Q_input_prev_old_(other.Q_input_prev_old_) + , Q_input_prev_new_(other.Q_input_prev_new_) , Qo_(other.Qo_) , Qn_(other.Qn_) , Po_(other.Po_) @@ -73,6 +75,8 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(const CoupledBound P_prev_sent_new_ = other.P_prev_sent_new_; Q_prev_sent_ = other.Q_prev_sent_; P_neu_prev_ = other.P_neu_prev_; + Q_input_prev_old_ = other.Q_input_prev_old_; + Q_input_prev_new_ = other.Q_input_prev_new_; Qo_ = other.Qo_; Qn_ = other.Qn_; Po_ = other.Po_; @@ -112,6 +116,8 @@ CoupledBoundaryCondition::CoupledBoundaryCondition(CoupledBoundaryCondition&& ot , P_prev_sent_new_(other.P_prev_sent_new_) , Q_prev_sent_(other.Q_prev_sent_) , P_neu_prev_(other.P_neu_prev_) + , Q_input_prev_old_(other.Q_input_prev_old_) + , Q_input_prev_new_(other.Q_input_prev_new_) , Qo_(other.Qo_) , Qn_(other.Qn_) , Po_(other.Po_) @@ -167,6 +173,8 @@ CoupledBoundaryCondition& CoupledBoundaryCondition::operator=(CoupledBoundaryCon P_prev_sent_new_ = other.P_prev_sent_new_; Q_prev_sent_ = other.Q_prev_sent_; P_neu_prev_ = other.P_neu_prev_; + Q_input_prev_old_ = other.Q_input_prev_old_; + Q_input_prev_new_ = other.Q_input_prev_new_; Qo_ = other.Qo_; Qn_ = other.Qn_; Po_ = other.Po_; diff --git a/Code/Source/solver/CoupledBoundaryCondition.h b/Code/Source/solver/CoupledBoundaryCondition.h index 9fcad5179..e3da28a71 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.h +++ b/Code/Source/solver/CoupledBoundaryCondition.h @@ -226,6 +226,8 @@ class CoupledBoundaryCondition { double P_prev_sent_new_ = 0.0; ///< Under-relaxed pressure sent at t_new on last 'L' step (DIR input). double Q_prev_sent_ = 0.0; ///< Under-relaxed flow output on last 'L' step (DIR output). double P_neu_prev_ = 0.0; ///< Under-relaxed pressure output on last 'L' step (NEU output). + double Q_input_prev_old_ = 0.0; ///< Under-relaxed flow sent at t_old on last 'L' step (NEU input). + double Q_input_prev_new_ = 0.0; ///< Under-relaxed flow sent at t_new on last 'L' step (NEU input). /// @brief Flowrate data double Qo_ = 0.0; ///< Flowrate at old timestep (t_n) @@ -386,6 +388,18 @@ class CoupledBoundaryCondition { /// @brief Set the NEU output pressure history (call on 'L' steps). void set_P_neu_prev(double P) { P_neu_prev_ = P; } + /// @brief Get the under-relaxed flow sent at t_old on the last 'L' step (NEU input). + double get_Q_input_prev_old() const { return Q_input_prev_old_; } + + /// @brief Get the under-relaxed flow sent at t_new on the last 'L' step (NEU input). + double get_Q_input_prev_new() const { return Q_input_prev_new_; } + + /// @brief Set the NEU input flow history (call on 'L' steps). + void set_Q_input_prev(double old_val, double new_val) { + Q_input_prev_old_ = old_val; + Q_input_prev_new_ = new_val; + } + /// @brief Set the svZeroD solution IDs for flow and pressure /// @param flow_id Flow solution ID /// @param pressure_id Pressure solution ID diff --git a/Code/Source/solver/svZeroD_interface.cpp b/Code/Source/solver/svZeroD_interface.cpp index 20e07741e..643b09c26 100644 --- a/Code/Source/solver/svZeroD_interface.cpp +++ b/Code/Source/solver/svZeroD_interface.cpp @@ -390,6 +390,8 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) // Temporary arrays to record relaxed values for history update on 'L' steps. std::vector P_sent_old_arr(numCoupledSrfs, 0.0); std::vector P_sent_new_arr(numCoupledSrfs, 0.0); + std::vector Q_input_sent_old_arr(numCoupledSrfs, 0.0); + std::vector Q_input_sent_new_arr(numCoupledSrfs, 0.0); std::vector Q_relaxed_arr(numCoupledSrfs, 0.0); std::vector P_relaxed_arr(numCoupledSrfs, 0.0); @@ -447,8 +449,28 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) P_sent_old_arr[i] = params[0]; P_sent_new_arr[i] = params[1]; } else { - params[0] = sign * QCoupled[i]; - params[1] = sign * QnCoupled[i]; + double raw_Q_old = sign * QCoupled[i]; + double raw_Q_new = sign * QnCoupled[i]; + + // Step 1: apply flow ramp (scales Q from ramp_ref over the first ramp_steps steps). + int ramp_steps = bc->coupled_bc.get_oned_ramp_steps(); + double Q_target_old, Q_target_new; + if (ramp_steps > 0) { + double ramp_factor = std::min(1.0, static_cast(bc->coupled_bc.get_ramp_step_count()) / ramp_steps); + double Q_ref = bc->coupled_bc.get_oned_ramp_ref_pressure(); // ref value (0.0 by default) + Q_target_old = Q_ref + ramp_factor * (raw_Q_old - Q_ref); + Q_target_new = Q_ref + ramp_factor * (raw_Q_new - Q_ref); + } else { + Q_target_old = raw_Q_old; + Q_target_new = raw_Q_new; + } + + // Step 2: apply under-relaxation. + const double omega = bc->coupled_bc.get_oned_relax_factor(); + params[0] = omega * Q_target_old + (1.0 - omega) * bc->coupled_bc.get_Q_input_prev_old(); + params[1] = omega * Q_target_new + (1.0 - omega) * bc->coupled_bc.get_Q_input_prev_new(); + Q_input_sent_old_arr[i] = params[0]; + Q_input_sent_new_arr[i] = params[1]; } update_svZeroD_block_params(svzd_blk_names[i], times, params); } @@ -507,6 +529,7 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) bc_hist->coupled_bc.set_P_prev_sent(P_sent_old_arr[i], P_sent_new_arr[i]); bc_hist->coupled_bc.set_Q_prev_sent(Q_relaxed_arr[i]); } else { + bc_hist->coupled_bc.set_Q_input_prev(Q_input_sent_old_arr[i], Q_input_sent_new_arr[i]); bc_hist->coupled_bc.set_P_neu_prev(P_relaxed_arr[i]); } bc_hist->coupled_bc.increment_ramp_step_count(); From afbbd467357b2701ccffa17d5c0e6d150c033cba Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Mon, 20 Apr 2026 16:30:02 -0700 Subject: [PATCH 40/51] fix bug --- Code/Source/solver/txt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Source/solver/txt.cpp b/Code/Source/solver/txt.cpp index 76f6b1308..ea637487c 100644 --- a/Code/Source/solver/txt.cpp +++ b/Code/Source/solver/txt.cpp @@ -200,7 +200,7 @@ void txt(Simulation* simulation, const bool init_write, const SolutionStates& so for (auto& bc : com_mod.eq[0].bc) { if (utils::btest(bc.bType, iBC_Coupled) && bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) { - bc.coupled_bc.compute_flowrates(com_mod, cm_mod); + bc.coupled_bc.compute_flowrates(com_mod, cm_mod, solutions); } } svOneD::calc_svOneD(com_mod, cm_mod, 'L'); From b74fc99b658e6c42a8a0f63d06bddb509de2b59c Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Thu, 30 Apr 2026 16:43:44 -0700 Subject: [PATCH 41/51] fix neumann coupling under relaxation and ramping --- Code/Source/solver/Parameters.h | 15 +++++---- Code/Source/solver/svOneD_subroutines.cpp | 39 +++++++++++++++++++---- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/Code/Source/solver/Parameters.h b/Code/Source/solver/Parameters.h index 3f2e45b1a..a90fb44a4 100644 --- a/Code/Source/solver/Parameters.h +++ b/Code/Source/solver/Parameters.h @@ -806,15 +806,18 @@ class CouplingInterfaceParameters : public ParameterLists // Path to the svOneDSolver .in input file for this face (1D coupling). Parameter svoned_input_file; - // Pressure ramp for 1D coupling initialization (DIR coupling only). - // Over the first Coupling_ramp_steps time steps the pressure passed to the - // 1D solver is linearly ramped from Coupling_ramp_ref_pressure to the - // actual 3D pressure. Set Coupling_ramp_steps = 0 (default) to disable. + // Ramp for 1D coupling initialization (both DIR and NEU coupling). + // Over the first Coupling_ramp_steps committed time steps the value passed + // to the 1D solver is linearly ramped: + // DIR: pressure P ramped from Coupling_ramp_ref_pressure to actual 3D P. + // NEU: flow rate Q ramped from 0 to actual 3D Q (ref pressure not used). + // Set Coupling_ramp_steps = 0 (default) to disable. Parameter coupling_ramp_steps; Parameter coupling_ramp_ref_pressure; - // Under-relaxation factor for pressure passed to the 1D solver (DIR coupling only). - // Applied as: P_sent = omega * P_new + (1 - omega) * P_prev_sent. + // Under-relaxation factor for the value passed to the 1D solver (both DIR and NEU coupling). + // DIR: P_sent = omega * P_target + (1 - omega) * P_prev_sent + // NEU: Q_sent = omega * Q_target + (1 - omega) * Q_prev_sent // Range: (0, 1]. Default 1.0 = no relaxation. Parameter coupling_relax_factor; diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index bffdf27bf..10a93ff5b 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -102,10 +102,13 @@ struct OneDModelState { // Index into eq[0].bc[] for the BC this model services. int iBc = -1; - // Pressure ramp for 1D coupling initialization (DIR coupling only). - // Over the first ramp_steps committed time steps the pressure sent to the - // 1D solver is linearly interpolated from ramp_ref_pressure to the actual - // 3D pressure value. Zero means no ramping. + // Pressure ramp for 1D coupling initialization. + // DIR: over the first ramp_steps committed time steps the pressure sent to + // the 1D solver is linearly interpolated from ramp_ref_pressure to the + // actual 3D pressure value. + // NEU: over the first ramp_steps committed time steps the flow rate sent to + // the 1D solver is linearly interpolated from 0 to the actual 3D Q. + // Zero means no ramping. int ramp_steps = 0; double ramp_ref_pressure = 0.0; int step_count = 0; ///< Number of committed (BCFlag=='L') steps taken. @@ -117,12 +120,15 @@ struct OneDModelState { // Output (Q from 1D) : Q_relax = omega * Q_raw + (1-omega) * Q_prev_sent // // NEU coupling: + // Input (Q sent to 1D): Q_sent = omega * Q_target + (1-omega) * Q_prev_sent // Output (P from 1D) : P_relax = omega * P_raw + (1-omega) * P_neu_prev double relax_factor = 1.0; double P_prev_sent_old = 0.0; ///< Under-relaxed pressure sent at params[3] (t_old) on last 'L' step (DIR). double P_prev_sent_new = 0.0; ///< Under-relaxed pressure sent at params[4] (t_new) on last 'L' step (DIR). double Q_prev_sent = 0.0; ///< Under-relaxed flow rate output on last 'L' step (DIR only). double P_neu_prev = 0.0; ///< Under-relaxed pressure output on last 'L' step (NEU only). + double Q_prev_sent_old = 0.0; ///< Under-relaxed flow rate sent at params[3] (t_old) on last 'L' step (NEU). + double Q_prev_sent_new = 0.0; ///< Under-relaxed flow rate sent at params[4] (t_new) on last 'L' step (NEU). }; // --------------------------------------------------------------------------- @@ -300,8 +306,24 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) params[2] = t_new; if (bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) { - params[3] = bc.coupled_bc.get_Qo(); - params[4] = bc.coupled_bc.get_Qn(); + double raw_Q_old = bc.coupled_bc.get_Qo(); + double raw_Q_new = bc.coupled_bc.get_Qn(); + // Step 1: apply flow rate ramp (scales amplitude from 0 to actual 3D + // flow rate over the first ramp_steps committed steps). + double Q_target_old, Q_target_new; + if (st.ramp_steps > 0) { + double ramp_factor = std::min(1.0, static_cast(st.step_count) / st.ramp_steps); + Q_target_old = ramp_factor * raw_Q_old; + Q_target_new = ramp_factor * raw_Q_new; + } else { + Q_target_old = raw_Q_old; + Q_target_new = raw_Q_new; + } + // Step 2: apply under-relaxation (damps timestep-to-timestep oscillations). + // Q_sent = omega * Q_target + (1 - omega) * Q_prev_sent + const double omega = st.relax_factor; + params[3] = omega * Q_target_old + (1.0 - omega) * st.Q_prev_sent_old; + params[4] = omega * Q_target_new + (1.0 - omega) * st.Q_prev_sent_new; } else { double raw_P_old = bc.coupled_bc.get_Po(); double raw_P_new = bc.coupled_bc.get_Pn(); @@ -349,7 +371,10 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) if (BCFlag == 'L') { st.solution = work_sol; // Update the under-relaxation history with the values actually sent. - if (st.coupling_type != "NEU") { + if (st.coupling_type == "NEU") { + st.Q_prev_sent_old = params[3]; + st.Q_prev_sent_new = params[4]; + } else { st.P_prev_sent_old = params[3]; st.P_prev_sent_new = params[4]; } From f01218a9f366bb8d10febe04f6694895c1b5bda8 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Wed, 6 May 2026 16:30:58 -0700 Subject: [PATCH 42/51] change Neu to Robin to avoid back flow --- Code/Source/solver/set_bc.cpp | 33 ++++++++++++- Code/Source/solver/svOneD_subroutines.cpp | 57 +++++++++++++---------- 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index 363fa803d..33a0f2f88 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -1539,7 +1539,38 @@ void set_bc_neu_l(ComMod& com_mod, const CmMod& cm_mod, const bcType& lBc, const } } - } else if (utils::btest(lBc.bType,iBC_res)) { + } else if (utils::btest(lBc.bType,iBC_Coupled)) { + // New-style Coupled NEU BC (svOneD/svZeroD): apply the actual 1D/0D + // pressure from the most recent 'D' solver call. bc.g is updated + // every Newton iteration by set_bc_cpl / calc_der_cpl_bc. + + //h(0) = lBc.g; + + double Q_3D = all_fun::integ(com_mod, cm_mod, lFa, Yn, eq.s, solutions, + eq.s+nsd-1, false, + consts::MechanicalConfigurationType::reference); + //h(0) = lBc.g - lBc.r * Q_3D; + + // Double-sided Robin: use |Q_3D| so that the correction always + // reduces h from P_1D, regardless of flow direction. + h(0) = lBc.g - lBc.r * std::abs(Q_3D); + // Backflow kinetic energy correction: when backflow is detected + // (Q < 0), subtract the face-averaged dynamic pressure to further + // reduce the applied traction and damp the incoming flow. + if (Q_3D < 0.0) { + int iM = lFa.iM; + int cDmn_local = all_fun::domain(com_mod, com_mod.msh[iM], cEq, lFa.gE(0)); + double rho = eq.dmn[cDmn_local].prop.at( + consts::PhysicalProperyType::fluid_density); + double beta = eq.dmn[cDmn_local].prop.at( + consts::PhysicalProperyType::backflow_stab); + double A = lFa.area; + if (A > 0.0) { + double u_n = Q_3D / A; // face-averaged normal velocity (< 0) + h(0) -= 0.5 * beta * rho * u_n * u_n; + } + } + } else if (utils::btest(lBc.bType,iBC_res)) { h(0) = lBc.r * all_fun::integ(com_mod, cm_mod, lFa, Yn, eq.s, solutions, eq.s+nsd-1, false, consts::MechanicalConfigurationType::reference); } else if (utils::btest(lBc.bType,iBC_std)) { diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_subroutines.cpp index 10a93ff5b..8789617a8 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_subroutines.cpp @@ -120,15 +120,15 @@ struct OneDModelState { // Output (Q from 1D) : Q_relax = omega * Q_raw + (1-omega) * Q_prev_sent // // NEU coupling: - // Input (Q sent to 1D): Q_sent = omega * Q_target + (1-omega) * Q_prev_sent - // Output (P from 1D) : P_relax = omega * P_raw + (1-omega) * P_neu_prev + // Output (P from 1D) : P_applied = omega * P_target + (1-omega) * P_neu_prev + // (P_target already includes the ramp from ramp_ref_pressure to P_raw) double relax_factor = 1.0; double P_prev_sent_old = 0.0; ///< Under-relaxed pressure sent at params[3] (t_old) on last 'L' step (DIR). double P_prev_sent_new = 0.0; ///< Under-relaxed pressure sent at params[4] (t_new) on last 'L' step (DIR). double Q_prev_sent = 0.0; ///< Under-relaxed flow rate output on last 'L' step (DIR only). - double P_neu_prev = 0.0; ///< Under-relaxed pressure output on last 'L' step (NEU only). - double Q_prev_sent_old = 0.0; ///< Under-relaxed flow rate sent at params[3] (t_old) on last 'L' step (NEU). - double Q_prev_sent_new = 0.0; ///< Under-relaxed flow rate sent at params[4] (t_new) on last 'L' step (NEU). + double P_neu_prev = 0.0; ///< Under-relaxed pressure applied to 3D on last 'L' step (NEU only). + double Q_prev_sent_old = 0.0; ///< Under-relaxed Q sent at params[3] (t_old) on last 'L' step (NEU). + double Q_prev_sent_new = 0.0; ///< Under-relaxed Q sent at params[4] (t_new) on last 'L' step (NEU). }; // --------------------------------------------------------------------------- @@ -306,24 +306,13 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) params[2] = t_new; if (bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) { - double raw_Q_old = bc.coupled_bc.get_Qo(); - double raw_Q_new = bc.coupled_bc.get_Qn(); - // Step 1: apply flow rate ramp (scales amplitude from 0 to actual 3D - // flow rate over the first ramp_steps committed steps). - double Q_target_old, Q_target_new; - if (st.ramp_steps > 0) { - double ramp_factor = std::min(1.0, static_cast(st.step_count) / st.ramp_steps); - Q_target_old = ramp_factor * raw_Q_old; - Q_target_new = ramp_factor * raw_Q_new; - } else { - Q_target_old = raw_Q_old; - Q_target_new = raw_Q_new; - } - // Step 2: apply under-relaxation (damps timestep-to-timestep oscillations). - // Q_sent = omega * Q_target + (1 - omega) * Q_prev_sent + // NEU coupling: apply under-relaxation to the 3D flow rate Q sent to + // the 1D solver to damp timestep-to-timestep oscillations in the input. + // No ramping is applied here; ramping is applied to the *output* pressure + // P that the 1D solver returns (Phase 2 below). const double omega = st.relax_factor; - params[3] = omega * Q_target_old + (1.0 - omega) * st.Q_prev_sent_old; - params[4] = omega * Q_target_new + (1.0 - omega) * st.Q_prev_sent_new; + params[3] = omega * bc.coupled_bc.get_Qo() + (1.0 - omega) * st.Q_prev_sent_new; + params[4] = omega * bc.coupled_bc.get_Qn() + (1.0 - omega) * st.Q_prev_sent_new; } else { double raw_P_old = bc.coupled_bc.get_Po(); double raw_P_new = bc.coupled_bc.get_Pn(); @@ -344,7 +333,7 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) // Step 2: apply under-relaxation (damps timestep-to-timestep oscillations). // P_sent = omega * P_target + (1 - omega) * P_prev_sent const double omega = st.relax_factor; - params[3] = omega * P_target_old + (1.0 - omega) * st.P_prev_sent_old; + params[3] = omega * P_target_old + (1.0 - omega) * st.P_prev_sent_new; params[4] = omega * P_target_new + (1.0 - omega) * st.P_prev_sent_new; } @@ -371,6 +360,8 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) if (BCFlag == 'L') { st.solution = work_sol; // Update the under-relaxation history with the values actually sent. + // NEU: Q_prev_sent updated here; P history updated in Phase 2. + // DIR: P_prev_sent tracks the pressure value sent to the 1D solver. if (st.coupling_type == "NEU") { st.Q_prev_sent_old = params[3]; st.Q_prev_sent_new = params[4]; @@ -408,9 +399,25 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) } } else { // 1D solver returns pressure P for NEU coupling. - // Apply under-relaxation to damp timestep-to-timestep oscillations. + // + // Step 1: apply pressure ramp (scales output P from ramp_ref_pressure + // to the actual 1D pressure over the first ramp_steps committed + // steps). This prevents a large sudden pressure jump from being + // imposed on the 3D domain at startup, which is the primary cause + // of oscillations in Neumann coupling. + double P_raw = cpl_values[k]; + double P_target; + if (st.ramp_steps > 0) { + double ramp_factor = std::min(1.0, static_cast(st.step_count) / st.ramp_steps); + double P_ref = st.ramp_ref_pressure; + P_target = P_ref + ramp_factor * (P_raw - P_ref); + } else { + P_target = P_raw; + } + // Step 2: apply under-relaxation to damp timestep-to-timestep oscillations. + // P_applied = omega * P_target + (1 - omega) * P_prev_applied const double omega = st.relax_factor; - double P_relaxed = omega * cpl_values[k] + (1.0 - omega) * st.P_neu_prev; + double P_relaxed = omega * P_target + (1.0 - omega) * st.P_neu_prev; cpl_bc.set_pressure(P_relaxed); if (BCFlag == 'L') { st.P_neu_prev = P_relaxed; From ff76805b49f64fab22f6b6af98d646aea238cc1d Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Tue, 12 May 2026 15:49:29 -0700 Subject: [PATCH 43/51] fix: remove robin part, keep only stabilization term. enough to converge --- Code/Source/solver/set_bc.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index 33a0f2f88..e283762b0 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -1549,11 +1549,11 @@ void set_bc_neu_l(ComMod& com_mod, const CmMod& cm_mod, const bcType& lBc, const double Q_3D = all_fun::integ(com_mod, cm_mod, lFa, Yn, eq.s, solutions, eq.s+nsd-1, false, consts::MechanicalConfigurationType::reference); - //h(0) = lBc.g - lBc.r * Q_3D; - // Double-sided Robin: use |Q_3D| so that the correction always - // reduces h from P_1D, regardless of flow direction. - h(0) = lBc.g - lBc.r * std::abs(Q_3D); + + h(0) = lBc.g; + //h(0) = lBc.g - lBc.r * std::abs(Q_3D); + // Backflow kinetic energy correction: when backflow is detected // (Q < 0), subtract the face-averaged dynamic pressure to further // reduce the applied traction and damp the incoming flow. From 42e3039e486579c2501ee0addb49bed799e2e854 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Fri, 15 May 2026 14:53:42 -0700 Subject: [PATCH 44/51] enhancement: available to use both 3D-1D coupling and 3D-0D coupling at the same model --- Code/Source/solver/CoupledBoundaryCondition.h | 2 + Code/Source/solver/distribute.cpp | 30 +--- Code/Source/solver/read_files.cpp | 147 ++++++++++++------ Code/Source/solver/set_bc.cpp | 52 ++++--- Code/Source/solver/svZeroD_interface.cpp | 8 +- Code/Source/solver/txt.cpp | 78 +++++----- 6 files changed, 176 insertions(+), 141 deletions(-) diff --git a/Code/Source/solver/CoupledBoundaryCondition.h b/Code/Source/solver/CoupledBoundaryCondition.h index e3da28a71..764877096 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.h +++ b/Code/Source/solver/CoupledBoundaryCondition.h @@ -333,6 +333,8 @@ class CoupledBoundaryCondition { /// @brief Set the svOneD input file path void set_oned_input_file(const std::string& path); + bool is_sv1d_face() const { return !oned_input_file_.empty(); } + /// @brief Get the pressure ramp step count (0 = disabled). int get_oned_ramp_steps() const { return oned_ramp_steps_; } diff --git a/Code/Source/solver/distribute.cpp b/Code/Source/solver/distribute.cpp index 4e06ecfc4..8adc11a35 100644 --- a/Code/Source/solver/distribute.cpp +++ b/Code/Source/solver/distribute.cpp @@ -557,38 +557,20 @@ void distribute(Simulation* simulation) cplBC.nX = 0; cplBC.xo.resize(cplBC.nX); } + } - } else if (cplBC.useSvZeroD) { - // Broadcast nX and xo: when RCR faces coexist with svZeroD, nX > 0 and xo must - // be distributed to all slave processes so rcr_init can access cplBC.xo[ptr]. - cm.bcast(cm_mod, &cplBC.nX); - if (cplBC.xo.size() == 0) { - cplBC.xo.resize(cplBC.nX); - } - if (cplBC.nX != 0) { - cm.bcast(cm_mod, cplBC.xo); - } - - } else if (cplBC.useSv1D) { + if (cplBC.useSv1D) { // Broadcast the sv1D solver interface data so that ALL ranks can call // init_svOneD / calc_svOneD (which use MPI_Bcast collectives that require // every rank to participate). Without this, slave processes have // has_data = false and throw immediately inside init_svOneD. cm.bcast(cm_mod, &cplBC.sv1d_solver_interface.has_data); cm.bcast(cm_mod, cplBC.sv1d_solver_interface.solver_library); + } - // Broadcast nX and xo: when RCR faces coexist with svOneD, nX > 0 and xo - // must be distributed to all slave processes so rcr_init works correctly. - cm.bcast(cm_mod, &cplBC.nX); - if (cplBC.xo.size() == 0) { - cplBC.xo.resize(cplBC.nX); - } - if (cplBC.nX != 0) { - cm.bcast(cm_mod, cplBC.xo); - } - - } else { - // RCR (Windkessel): nX/xo sized in read_files from nFa; not genBC/svZeroD/svOneD. + if (!cplBC.useGenBC) { + // Broadcast nX and xo: when RCR faces coexist with svZeroD/svOneD, nX > 0 + // and xo must be distributed to all slave processes so rcr_init works correctly. cm.bcast(cm_mod, &cplBC.nX); if (cplBC.xo.size() == 0) { cplBC.xo.resize(cplBC.nX); diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 610652800..c092624d1 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -236,46 +236,53 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, read_fourier_coeff_values_file(file_name, lBc); } - // Coupling to a 0D model: + // Coupling to a 0D/1D model: // - GenBC / cplBC: Time_dependence Coupled without (cplBC.fa). // - svZeroDSolver: Time_dependence Coupled + (CoupledBoundaryCondition). // } else if (ctmp == "Coupled") { auto& face_name = com_mod.msh[lBc.iM].fa[lBc.iFa].name; const bool svzd_iface = com_mod.cplBC.svzerod_solver_interface.has_data; + const bool sv1d_iface = com_mod.cplBC.sv1d_solver_interface.has_data; const bool ci_set = bc_params->coupling_interface.value_set; - const bool ci_has_block = ci_set && bc_params->coupling_interface.svzerod_solver_block.defined(); - - if (svzd_iface) { - // Coupled BC to svZeroDSolver - lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_Coupled)); - lBc.bType = utils::ibclr(lBc.bType, enum_int(BoundaryConditionType::bType_bfs)); + const bool ci_has_block = ci_set && bc_params->coupling_interface.svzerod_solver_block.defined(); + const bool ci_has_1d_file = ci_set && bc_params->coupling_interface.svoned_input_file.defined(); // Sanity check: must define - if (!ci_has_block) { - if (ci_set) { - throw std::runtime_error(std::string("[read_bc] on face '") + face_name + - "' must define ."); - } + if (svzd_iface || sv1d_iface) { + // svZeroD and/or svOneD path: route each face individually based on its + // content. + + if (ci_has_block && ci_has_1d_file) { throw std::runtime_error( - std::string("[read_bc] With , each svZeroD-coupled face needs " - " with (Time_dependence Coupled) on face '") + - face_name + "'."); + std::string("[read_bc] on face '") + face_name + + "' defines both and . " + "Specify exactly one per face."); } - } else { - // genBC / cplBC / svOneD path. - const bool sv1d_iface = com_mod.cplBC.sv1d_solver_interface.has_data; + if (ci_has_block) { + // 0D face: route to svZeroD. + if (!svzd_iface) { + throw std::runtime_error( + std::string("[read_bc] Face '") + face_name + + "' specifies but no " + "is defined on the equation."); + } + lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_Coupled)); + lBc.bType = utils::ibclr(lBc.bType, enum_int(BoundaryConditionType::bType_Dir)); + lBc.bType = utils::ibclr(lBc.bType, enum_int(BoundaryConditionType::bType_Neu)); + lBc.bType = utils::ibclr(lBc.bType, enum_int(BoundaryConditionType::bType_bfs)); + lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_cpl)); + // oned_input_file stays empty — marks this as a 0D face in the + // CoupledBoundaryCondition construction block below. - if (sv1d_iface) { - // For svOneD: each coupled face must specify its own 1D input file via - // ... - if (!ci_set || !bc_params->coupling_interface.svoned_input_file.defined()) { + } else if (ci_has_1d_file) { + // 1D face: route to svOneD. + if (!sv1d_iface) { throw std::runtime_error( - std::string("[read_bc] With , each 1D-coupled face needs " - " with (Time_dependence Coupled) " - "on face '") + - face_name + "'."); + std::string("[read_bc] Face '") + face_name + + "' specifies but no " + "is defined on the equation."); } lBc.oned_input_file = bc_params->coupling_interface.svoned_input_file.value(); @@ -295,26 +302,46 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, } } else { - // genBC / cplBC: uses cplBC.fa array mechanism. + // is present but has neither field, or is absent entirely. if (ci_set) { throw std::runtime_error( - "[read_bc] is only valid when or " - " is defined on the equation."); + std::string("[read_bc] on face '") + face_name + + "' must define either (for 0D coupling) or " + " (for 1D coupling)."); } - - - lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_cpl)); - com_mod.cplBC.nFa = com_mod.cplBC.nFa + 1; - lBc.cplBCptr = com_mod.cplBC.nFa - 1; - - if (com_mod.cplBC.schm == CplBCType::cplBC_NA) { + if (svzd_iface) { throw std::runtime_error( - std::string("[read_bc] A coupling method (e.g. Couple_to_genBC) must be defined for Time_dependence " - "Coupled on face '") + + std::string("[read_bc] With , each svZeroD-coupled face needs " + " with " + "(Time_dependence Coupled) on face '") + + face_name + "'."); + } else { + throw std::runtime_error( + std::string("[read_bc] With , each 1D-coupled face needs " + " with " + "(Time_dependence Coupled) on face '") + face_name + "'."); } } + } else { + // genBC / cplBC path: no svZeroD or svOneD interface defined. + if (bc_params->coupling_interface.value_set) { + throw std::runtime_error( + "[read_bc] is only valid when or " + " is defined on the equation."); + } + + lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_cpl)); + com_mod.cplBC.nFa = com_mod.cplBC.nFa + 1; + lBc.cplBCptr = com_mod.cplBC.nFa - 1; + + if (com_mod.cplBC.schm == CplBCType::cplBC_NA) { + throw std::runtime_error( + std::string("[read_bc] A coupling method (e.g. Couple_to_genBC) must be defined for Time_dependence " + "Coupled on face '") + + face_name + "'."); + } } } else if (ctmp == "Resistance") { @@ -444,7 +471,9 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, face_name + "'."); } - if (com_mod.cplBC.sv1d_solver_interface.has_data) { + const bool is_sv1d_face = !lBc.oned_input_file.empty(); + + if (is_sv1d_face) { // ------------------------------------------------------------------ // svOneD path: construct CoupledBoundaryCondition with empty // block_name and store the 1D input file path. @@ -1549,14 +1578,33 @@ void read_eq(Simulation* simulation, EquationParameters* eq_params, eqType& lEq) cplBC.useGenBC = true; cplbc_type_str = eq_params->couple_to_genBC.type.value(); - } else if (eq_params->svzerodsolver_interface_parameters.defined()) { - cplBC.useSvZeroD = true; - cplbc_type_str = eq_params->svzerodsolver_interface_parameters.coupling_type.value(); - cplBC.svzerod_solver_interface.set_data(eq_params->svzerodsolver_interface_parameters); - } else if (eq_params->svonedsolver_interface_parameters.defined()) { - cplBC.useSv1D = true; - cplbc_type_str = eq_params->svonedsolver_interface_parameters.coupling_type.value(); - cplBC.sv1d_solver_interface.set_data(eq_params->svonedsolver_interface_parameters); + } else { + // Allow svZeroDSolver_interface and svOneDSolver_interface to coexist + // (mixed coupling: some faces go to 0D, others to 1D). + // + // Processing order: svZeroD first, then svOneD. + // cplbc_type_str is set by whichever interface is processed first; + // the second interface must match that type. + if (eq_params->svzerodsolver_interface_parameters.defined()) { + cplBC.useSvZeroD = true; + cplbc_type_str = eq_params->svzerodsolver_interface_parameters.coupling_type.value(); + cplBC.svzerod_solver_interface.set_data(eq_params->svzerodsolver_interface_parameters); + } + + if (eq_params->svonedsolver_interface_parameters.defined()) { + const std::string sv1d_type = eq_params->svonedsolver_interface_parameters.coupling_type.value(); + // When both interfaces are defined, their Coupling_type must match. + if (!cplbc_type_str.empty() && cplbc_type_str != sv1d_type) { + throw std::runtime_error( + "[read_eq] svZeroDSolver_interface and svOneDSolver_interface must use the same " + "Coupling_type in a mixed-coupling simulation " + "(svZeroDSolver_interface has '" + cplbc_type_str + + "', svOneDSolver_interface has '" + sv1d_type + "')."); + } + cplbc_type_str = sv1d_type; + cplBC.useSv1D = true; + cplBC.sv1d_solver_interface.set_data(eq_params->svonedsolver_interface_parameters); + } } if (eq_params->couple_to_genBC.defined() || @@ -1578,10 +1626,7 @@ void read_eq(Simulation* simulation, EquationParameters* eq_params, eqType& lEq) cplBC.nX = 0; cplBC.xp.resize(cplBC.nX); - } else if (cplBC.useSvZeroD) { - cplBC.nX = 0; - - } else if (cplBC.useSv1D) { + } else { cplBC.nX = 0; } } diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index e283762b0..387c7b9d2 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -179,20 +179,21 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& // Call genBC or cplBC to get updated pressures or flowrates. if (cplBC.useGenBC) { set_bc::genBC_Integ_X(com_mod, cm_mod, "D"); - } else if (cplBC.useSvZeroD) { - svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); - // Also integrate any RCR faces that coexist with svZeroD faces. - if (RCRflag) { - set_bc::cplBC_Integ_X(com_mod, cm_mod, true); - } - } else if (cplBC.useSv1D) { - svOneD::calc_svOneD(com_mod, cm_mod, 'D'); - // Also integrate any RCR faces that coexist with svOneD faces. - if (RCRflag) { - set_bc::cplBC_Integ_X(com_mod, cm_mod, true); - } } else { - set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); + // In mixed-coupling simulations both useSvZeroD and useSv1D can be true. + // Call each active solver independently. + if (cplBC.useSvZeroD) { + svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); + } + if (cplBC.useSv1D) { + svOneD::calc_svOneD(com_mod, cm_mod, 'D'); + } + if (!cplBC.useSvZeroD && !cplBC.useSv1D) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); + } else if (RCRflag) { + // Also integrate any RCR faces that coexist with svZeroD/svOneD faces. + set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } } // Compute the epsilon parameter (diff) for the finite difference calculation @@ -290,7 +291,7 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& // Perturb flowrate and compute new pressure bc.coupled_bc.perturb_flowrate(diff); - if (cplBC.useSv1D) { + if (bc.coupled_bc.is_sv1d_face()) { svOneD::calc_svOneD(com_mod, cm_mod, 'D'); } else { svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); @@ -845,20 +846,21 @@ void set_bc_cpl(ComMod& com_mod, CmMod& cm_mod, const SolutionStates& solutions) // Updates pressure or flowrates stored in cplBC.fa[i].y if (cplBC.useGenBC) { set_bc::genBC_Integ_X(com_mod, cm_mod, "D"); - } else if (cplBC.useSvZeroD){ - svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); - // Also integrate any RCR faces that coexist with svZeroD faces. - if (RCRflag) { - set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } else { + // In mixed-coupling simulations both useSvZeroD and useSv1D can be true. + // Call each active solver independently. + if (cplBC.useSvZeroD) { + svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); } - } else if (cplBC.useSv1D) { - svOneD::calc_svOneD(com_mod, cm_mod, 'D'); - // Also integrate any RCR faces that coexist with svOneD faces. - if (RCRflag) { + if (cplBC.useSv1D) { + svOneD::calc_svOneD(com_mod, cm_mod, 'D'); + } + if (!cplBC.useSvZeroD && !cplBC.useSv1D) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); + } else if (RCRflag) { + // Also integrate any RCR faces that coexist with svZeroD/svOneD faces. set_bc::cplBC_Integ_X(com_mod, cm_mod, true); } - } else { - set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); } } diff --git a/Code/Source/solver/svZeroD_interface.cpp b/Code/Source/solver/svZeroD_interface.cpp index 643b09c26..6d9898736 100644 --- a/Code/Source/solver/svZeroD_interface.cpp +++ b/Code/Source/solver/svZeroD_interface.cpp @@ -27,7 +27,13 @@ static void build_svzero_coupled_bc_idxs(ComMod& com_mod) cpl.svZeroD_coupled_bc_idxs.clear(); for (int iEq = 0; iEq < com_mod.nEq; iEq++) { for (int iBc = 0; iBc < com_mod.eq[iEq].nBc; iBc++) { - if (utils::btest(com_mod.eq[iEq].bc[iBc].bType, consts::iBC_Coupled)) { + const auto& bc = com_mod.eq[iEq].bc[iBc]; + // A Coupled face belongs to svZeroD only when it is not a 1D face. + // The is_sv1d_face() helper encapsulates the routing invariant: + // 1D face → oned_input_file_ non-empty, block_name_ empty + // 0D face → block_name_ non-empty, oned_input_file_ empty + if (utils::btest(bc.bType, consts::iBC_Coupled) && + !bc.coupled_bc.is_sv1d_face()) { cpl.svZeroD_coupled_bc_idxs.emplace_back(iEq, iBc); } } diff --git a/Code/Source/solver/txt.cpp b/Code/Source/solver/txt.cpp index ea637487c..aaa316ebb 100644 --- a/Code/Source/solver/txt.cpp +++ b/Code/Source/solver/txt.cpp @@ -175,54 +175,52 @@ void txt(Simulation* simulation, const bool init_write, const SolutionStates& so if (!init_write) { if (cplBC.useGenBC) { set_bc::genBC_Integ_X(com_mod, cm_mod, "L"); - } else if (cplBC.useSvZeroD) { - svZeroD::calc_svZeroD(com_mod, cm_mod, 'L'); - // Also integrate any RCR faces that coexist with svZeroD faces. - for (auto& bc : com_mod.eq[0].bc) { - if (utils::btest(bc.bType, iBC_RCR)) { - ltmp = true; - break; - } - } - if (ltmp) { - set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } else { + // In mixed-coupling simulations both useSvZeroD and useSv1D can be true. + // Call each active solver independently. + if (cplBC.useSvZeroD) { + svZeroD::calc_svZeroD(com_mod, cm_mod, 'L'); } - } else if (cplBC.useSv1D) { - // Update NEU coupling flowrates from the final converged velocity - // field (Yn) before committing the 1D solution. During the Newton - // loop, compute_flowrates() is called at the *start* of each - // iteration using a partially-converged Yn. After the last picc - // correction Yn is fully converged, but Qn_ is never refreshed. - // Re-computing here ensures the 1D solver receives the same - // flowrate that write_boundary_integral_data() reports in - // B_NS_Velocity_flux.txt. - for (auto& bc : com_mod.eq[0].bc) { - if (utils::btest(bc.bType, iBC_Coupled) && - bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) { - bc.coupled_bc.compute_flowrates(com_mod, cm_mod, solutions); + if (cplBC.useSv1D) { + // Update NEU coupling flowrates from the final converged velocity + // field (Yn) before committing the 1D solution. During the Newton + // loop, compute_flowrates() is called at the *start* of each + // iteration using a partially-converged Yn. After the last picc + // correction Yn is fully converged, but Qn_ is never refreshed. + // Re-computing here ensures the 1D solver receives the same + // flowrate that write_boundary_integral_data() reports in + // B_NS_Velocity_flux.txt. + for (auto& bc : com_mod.eq[0].bc) { + if (utils::btest(bc.bType, iBC_Coupled) && + bc.coupled_bc.is_sv1d_face() && + bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) { + bc.coupled_bc.compute_flowrates(com_mod, cm_mod, solutions); + } } - } svOneD::calc_svOneD(com_mod, cm_mod, 'L'); - // Also integrate any RCR faces that coexist with svOneD faces. - for (auto& bc : com_mod.eq[0].bc) { - if (utils::btest(bc.bType, iBC_RCR)) { - ltmp = true; - break; - } - } - if (ltmp) { - set_bc::cplBC_Integ_X(com_mod, cm_mod, true); } - } else { - for (auto& bc : com_mod.eq[0].bc) { - if (utils::btest(bc.bType, iBC_RCR)) { - ltmp = true; - break; + // Also integrate any RCR faces coexisting with svZeroD/svOneD faces. + if (cplBC.useSvZeroD || cplBC.useSv1D) { + for (auto& bc : com_mod.eq[0].bc) { + if (utils::btest(bc.bType, iBC_RCR)) { + ltmp = true; + break; + } + } + if (ltmp) { + set_bc::cplBC_Integ_X(com_mod, cm_mod, true); + } + } else { + for (auto& bc : com_mod.eq[0].bc) { + if (utils::btest(bc.bType, iBC_RCR)) { + ltmp = true; + break; + } } + set_bc::cplBC_Integ_X(com_mod, cm_mod, ltmp); } - set_bc::cplBC_Integ_X(com_mod, cm_mod, ltmp); } } } From ab9872afabd10ea28bfc12b8466125bdea587297 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Thu, 4 Jun 2026 14:51:14 -0700 Subject: [PATCH 45/51] fix: remove stale TTPInitialConditionsParameters reference --- Code/Source/solver/Parameters.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Code/Source/solver/Parameters.h b/Code/Source/solver/Parameters.h index a90fb44a4..2176cf587 100644 --- a/Code/Source/solver/Parameters.h +++ b/Code/Source/solver/Parameters.h @@ -1405,7 +1405,6 @@ class DomainParameters : public ParameterLists StimulusParameters stimulus; FluidViscosityParameters fluid_viscosity; SolidViscosityParameters solid_viscosity; - TTPInitialConditionsParameters ttp_initial_conditions; // Ionic model parameters. std::map> ionic_models; From 3ed2c50ec528c30bcbba78d2b7684d6b01ef01cc Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Thu, 4 Jun 2026 14:55:43 -0700 Subject: [PATCH 46/51] fix: remove stale TTP initial condition parameter code --- Code/Source/solver/Parameters.cpp | 181 ------------------------------ 1 file changed, 181 deletions(-) diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index 2434d97c3..b07b6242b 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -1976,187 +1976,6 @@ void DomainParameters::set_values(tinyxml2::XMLElement* domain_elem, bool from_e */ } -////////////////////////////////////////////////////////// -// TTPInitialConditionsParameters // -////////////////////////////////////////////////////////// - -const std::string TTPInitialConditionsParameters::xml_element_name_ = "TTP_initial_conditions"; - -TTPInitialConditionsParameters::TTPInitialConditionsParameters() -{ -} - -void TTPInitialConditionsParameters::print_parameters() -{ - if (value_set) { - std::cout << std::endl; - std::cout << "TTP Initial Conditions Parameters" << std::endl; - std::cout << "---------------------------------" << std::endl; - initial_states.print_parameters(); - gating_variables.print_parameters(); - } -} - -void TTPInitialConditionsParameters::set_values(tinyxml2::XMLElement* xml_elem) -{ - using namespace tinyxml2; - std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; - - auto item = xml_elem->FirstChildElement(); - - while (item != nullptr) { - auto name = std::string(item->Value()); - - if (name == TTPInitialStatesParameters::xml_element_name_) { - initial_states.set_values(item); - value_set = true; - - } else if (name == TTPGatingVariablesParameters::xml_element_name_) { - gating_variables.set_values(item); - value_set = true; - - } else { - throw std::runtime_error(error_msg + name + "'."); - } - - item = item->NextSiblingElement(); - } - - if (!initial_states.defined()) { - throw std::runtime_error(xml_element_name_ + " requires an '" + - TTPInitialStatesParameters::xml_element_name_ + "' XML section."); - } - - if (!gating_variables.defined()) { - throw std::runtime_error(xml_element_name_ + " requires a '" + - TTPGatingVariablesParameters::xml_element_name_ + "' XML section."); - } -} - -////////////////////////////////////////////////////////// -// TTPInitialStatesParameters // -////////////////////////////////////////////////////////// - -const std::string TTPInitialStatesParameters::xml_element_name_ = "Initial_states"; - -TTPInitialStatesParameters::TTPInitialStatesParameters() -{ - bool required = true; - - set_parameter("V", -85.23, required, V); - set_parameter("K_i", 136.89, required, K_i); - set_parameter("Na_i", 8.6040, required, Na_i); - set_parameter("Ca_i", 1.26E-4, required, Ca_i); - set_parameter("Ca_ss", 3.6E-4, required, Ca_ss); - set_parameter("Ca_sr", 3.64, required, Ca_sr); - set_parameter("R_bar", 0.9073, required, R_bar); -} - -void TTPInitialStatesParameters::print_parameters() -{ - if (value_set) { - std::cout << " Initial States:" << std::endl; - auto params_name_value = get_parameter_list(); - for (auto& [key, value] : params_name_value) { - std::cout << " " << key << ": " << value << std::endl; - } - } -} - -void TTPInitialStatesParameters::set_values(tinyxml2::XMLElement* xml_elem) -{ - using namespace tinyxml2; - std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; - - auto item = xml_elem->FirstChildElement(); - - while (item != nullptr) { - auto name = std::string(item->Value()); - - if (item->GetText() != nullptr) { - auto value = item->GetText(); - try { - set_parameter_value(name, value); - value_set = true; - } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); - } - } else { - throw std::runtime_error(error_msg + name + "'."); - } - - item = item->NextSiblingElement(); - } - - check_required(); -} - -////////////////////////////////////////////////////////// -// TTPGatingVariablesParameters // -////////////////////////////////////////////////////////// - -const std::string TTPGatingVariablesParameters::xml_element_name_ = "Gating_variables"; - -TTPGatingVariablesParameters::TTPGatingVariablesParameters() -{ - bool required = true; - - set_parameter("x_r1_rectifier", 6.21E-3, required, x_r1_rectifier); - set_parameter("x_r2_rectifier", 0.4712, required, x_r2_rectifier); - set_parameter("x_s_rectifier", 9.5E-3, required, x_s_rectifier); - - set_parameter("m_fast_Na", 1.72E-3, required, m_fast_Na); - set_parameter("h_fast_Na", 0.7444, required, h_fast_Na); - set_parameter("j_fast_Na", 0.7045, required, j_fast_Na); - - set_parameter("d_slow_in", 3.373E-5, required, d_slow_in); - set_parameter("f_slow_in", 0.7888, required, f_slow_in); - set_parameter("f2_slow_in", 0.9755, required, f2_slow_in); - set_parameter("fcass_slow_in", 0.9953, required, fcass_slow_in); - - set_parameter("s_out", 0.999998, required, s_out); - set_parameter("r_out", 2.42E-8, required, r_out); -} - -void TTPGatingVariablesParameters::print_parameters() -{ - if (value_set) { - std::cout << " Gating Variables:" << std::endl; - auto params_name_value = get_parameter_list(); - for (auto& [key, value] : params_name_value) { - std::cout << " " << key << ": " << value << std::endl; - } - } -} - -void TTPGatingVariablesParameters::set_values(tinyxml2::XMLElement* xml_elem) -{ - using namespace tinyxml2; - std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; - - auto item = xml_elem->FirstChildElement(); - - while (item != nullptr) { - auto name = std::string(item->Value()); - - if (item->GetText() != nullptr) { - auto value = item->GetText(); - try { - set_parameter_value(name, value); - value_set = true; - } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); - } - } else { - throw std::runtime_error(error_msg + name + "'."); - } - - item = item->NextSiblingElement(); - } - - check_required(); -} - ////////////////////////////////////////////////////////// // DirectionalDistributionParameters // ////////////////////////////////////////////////////////// From 9b8675dae03556518ddae1bbce551a17a5d648ce Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Thu, 4 Jun 2026 15:05:36 -0700 Subject: [PATCH 47/51] fix: restore coupled_bc_type initialization after mixed 0D/1D merge --- Code/Source/solver/read_files.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index c092624d1..e2fc91a7c 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -128,12 +128,15 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, { using namespace consts; auto bc_type = bc_params->type.value(); + BoundaryConditionType coupled_bc_type = BoundaryConditionType::bType_Neu; if (std::set{"Dirichlet", "Dir"}.count(bc_type)) { - lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_Dir)); + lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_Dir)); + coupled_bc_type = BoundaryConditionType::bType_Dir; } else if (std::set{"Neumann", "Neu"}.count(bc_type)) { lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_Neu)); + coupled_bc_type = BoundaryConditionType::bType_Neu; if ((lEq.phys == EquationType::phys_fluid) || (lEq.phys == EquationType::phys_FSI)) { lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_bfs)); } From 3cc56374c8cfec95fbe27dae95942eedcdd416d4 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Thu, 4 Jun 2026 15:09:24 -0700 Subject: [PATCH 48/51] fix: remove stale static CmMod definition --- Code/Source/solver/CoupledBoundaryCondition.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Code/Source/solver/CoupledBoundaryCondition.cpp b/Code/Source/solver/CoupledBoundaryCondition.cpp index dabd324ca..0f4ce18af 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.cpp +++ b/Code/Source/solver/CoupledBoundaryCondition.cpp @@ -16,8 +16,6 @@ // CoupledBoundaryCondition // ========================================================================= -const CmMod CoupledBoundaryCondition::cm_mod_{}; - CoupledBoundaryCondition::CoupledBoundaryCondition(const CoupledBoundaryCondition& other) : face_(other.face_) , cap_face_vtp_file_(other.cap_face_vtp_file_) From 4b968c1c0475a918298433164bf5ef6cbd2c0bd1 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Thu, 4 Jun 2026 15:22:32 -0700 Subject: [PATCH 49/51] remove coupling fom the parameter naming --- Code/Source/solver/Parameters.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index b07b6242b..4b145f80f 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -469,9 +469,9 @@ CouplingInterfaceParameters::CouplingInterfaceParameters() set_parameter("svZeroDSolver_block", "", !required, svzerod_solver_block); set_parameter("Chamber_cap_surface", "", !required, chamber_cap_surface); set_parameter("svOneDSolver_input_file","", !required, svoned_input_file); - set_parameter("Coupling_ramp_steps", 0, !required, coupling_ramp_steps); - set_parameter("Coupling_ramp_ref_pressure", 0.0, !required, coupling_ramp_ref_pressure); - set_parameter("Coupling_relax_factor", 1.0, !required, coupling_relax_factor); + set_parameter("Ramp_steps", 0, !required, coupling_ramp_steps); + set_parameter("Ramp_ref_pressure", 0.0, !required, coupling_ramp_ref_pressure); + set_parameter("Relax_factor", 1.0, !required, coupling_relax_factor); } void CouplingInterfaceParameters::set_values(tinyxml2::XMLElement* xml_elem) From 99e2944085662b4611012da181fb66b1fd361766 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Wed, 17 Jun 2026 17:16:49 -0700 Subject: [PATCH 50/51] save review changes before clean up --- Code/Source/solver/CMakeLists.txt | 2 +- Code/Source/solver/ComMod.cpp | 6 +- Code/Source/solver/ComMod.h | 17 +- .../solver/CoupledBoundaryCondition.cpp | 62 +------ Code/Source/solver/CoupledBoundaryCondition.h | 10 +- Code/Source/solver/Parameters.h | 13 +- Code/Source/solver/baf_ini.cpp | 7 +- Code/Source/solver/distribute.cpp | 10 +- Code/Source/solver/read_files.cpp | 157 ++++++++++-------- Code/Source/solver/set_bc.cpp | 18 +- ...D_subroutines.cpp => svOneD_interface.cpp} | 56 +++++-- ...vOneD_subroutines.h => svOneD_interface.h} | 0 .../svOneD_interface/OneDSolverInterface.cpp | 63 +++++-- .../svOneD_interface/OneDSolverInterface.h | 63 +++++-- Code/Source/solver/svZeroD_interface.cpp | 6 +- Code/Source/solver/txt.cpp | 16 +- .../fluid/Tpipe_svOneD_2faces/inlet_1d.in | 125 ++++++++++++++ .../Tpipe_svOneD_2faces/inlet_1d_00050.vtp | 3 + .../mesh-complete/mesh-complete.exterior.vtp | 3 + .../mesh-complete/mesh-complete.mesh.vtu | 3 + .../mesh-surfaces/cap_main_3d.vtp | 3 + .../mesh-surfaces/cap_main_3d_2.vtp | 3 + .../mesh-surfaces/cap_side_3d.vtp | 3 + .../mesh-surfaces/wall_main_3d.vtp | 3 + .../mesh-surfaces/wall_side_3d.vtp | 3 + .../mesh-complete/walls_combined.vtp | 3 + .../fluid/Tpipe_svOneD_2faces/outlet_1d.in | 137 +++++++++++++++ .../Tpipe_svOneD_2faces/outlet_1d_00050.vtp | 3 + .../fluid/Tpipe_svOneD_2faces/result_050.vtu | 3 + .../fluid/Tpipe_svOneD_2faces/solver.xml | 112 +++++++++++++ .../mesh-complete/mesh-complete.exterior.vtp | 3 + .../mesh-complete/mesh-complete.mesh.vtu | 3 + .../mesh-surfaces/cap_main_3d.vtp | 3 + .../mesh-surfaces/cap_main_3d_2.vtp | 3 + .../mesh-surfaces/cap_side_3d.vtp | 3 + .../mesh-surfaces/wall_main_3d.vtp | 3 + .../mesh-surfaces/wall_side_3d.vtp | 3 + .../mesh-complete/walls_combined.vtp | 3 + .../fluid/Tpipe_svZeroD_Dir/result_050.vtu | 3 + .../cases/fluid/Tpipe_svZeroD_Dir/solver.xml | 110 ++++++++++++ .../fluid/Tpipe_svZeroD_Dir/solver_0d.json | 52 ++++++ .../cases/fluid/pipe_svOneD_2faces/solver.xml | 142 ---------------- 42 files changed, 876 insertions(+), 368 deletions(-) rename Code/Source/solver/{svOneD_subroutines.cpp => svOneD_interface.cpp} (92%) rename Code/Source/solver/{svOneD_subroutines.h => svOneD_interface.h} (100%) create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/inlet_1d.in create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/inlet_1d_00050.vtp create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-complete.exterior.vtp create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-complete.mesh.vtu create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_main_3d.vtp create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_main_3d_2.vtp create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_side_3d.vtp create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/wall_main_3d.vtp create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/wall_side_3d.vtp create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/walls_combined.vtp create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/outlet_1d.in create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/outlet_1d_00050.vtp create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/result_050.vtu create mode 100644 tests/cases/fluid/Tpipe_svOneD_2faces/solver.xml create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-complete.exterior.vtp create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-complete.mesh.vtu create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_main_3d.vtp create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_main_3d_2.vtp create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_side_3d.vtp create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/wall_main_3d.vtp create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/wall_side_3d.vtp create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/walls_combined.vtp create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/result_050.vtu create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/solver.xml create mode 100644 tests/cases/fluid/Tpipe_svZeroD_Dir/solver_0d.json delete mode 100644 tests/cases/fluid/pipe_svOneD_2faces/solver.xml diff --git a/Code/Source/solver/CMakeLists.txt b/Code/Source/solver/CMakeLists.txt index 05a3f1e9c..bb2c50fed 100644 --- a/Code/Source/solver/CMakeLists.txt +++ b/Code/Source/solver/CMakeLists.txt @@ -204,7 +204,7 @@ set(CSRCS stokes.h stokes.cpp sv_struct.h sv_struct.cpp svZeroD_interface.h svZeroD_interface.cpp - svOneD_subroutines.h svOneD_subroutines.cpp + svOneD_interface.h svOneD_interface.cpp txt.h txt.cpp utils.h utils.cpp ustruct.h ustruct.cpp diff --git a/Code/Source/solver/ComMod.cpp b/Code/Source/solver/ComMod.cpp index 7d543dd15..93d8d8be7 100644 --- a/Code/Source/solver/ComMod.cpp +++ b/Code/Source/solver/ComMod.cpp @@ -190,10 +190,10 @@ rmshType::rmshType() ///////////////////////////////////////////////////////// -// s v Z e r o D S o l v e r I n t e r f a c e T y p e // +// s v Z e r o D S o l v e r I n t e r f a c e D a t a // ///////////////////////////////////////////////////////// -void svZeroDSolverInterfaceType::set_data(const svZeroDSolverInterfaceParameters& params) +void svZeroDSolverInterfaceData::set_data(const svZeroDSolverInterfaceParameters& params) { if (!params.defined()) { return; @@ -218,7 +218,7 @@ void svZeroDSolverInterfaceType::set_data(const svZeroDSolverInterfaceParameters has_data = true; } -void svOneDSolverInterfaceType::set_data(const svOneDSolverInterfaceParameters& params) +void svOneDSolverInterfaceData::set_data(const svOneDSolverInterfaceParameters& params) { if (!params.defined()) { return; diff --git a/Code/Source/solver/ComMod.h b/Code/Source/solver/ComMod.h index a920a1e5d..444f5e467 100644 --- a/Code/Source/solver/ComMod.h +++ b/Code/Source/solver/ComMod.h @@ -784,12 +784,12 @@ class cplFaceType }; //---------------------------- -// svZeroDSolverInterfaceType +// svZeroDSolverInterfaceData //---------------------------- // This class stores information used to interface to // the svZeroDSolver. // -class svZeroDSolverInterfaceType +class svZeroDSolverInterfaceData { public: @@ -818,11 +818,11 @@ class svZeroDSolverInterfaceType }; //---------------------------- -// svOneDSolverInterfaceType +// svOneDSolverInterfaceData //---------------------------- // This class stores information used to interface to the svOneDSolver. // -class svOneDSolverInterfaceType +class svOneDSolverInterfaceData { public: // Path to the 1D solver shared library (without .so/.dylib extension, @@ -834,6 +834,7 @@ class svOneDSolverInterfaceType // Note: the per-face input files are stored in cplFaceType::oned_input_file. bool has_data = false; + // Read svOneDSolver interface information from parameters. void set_data(const svOneDSolverInterfaceParameters& params); }; @@ -853,7 +854,7 @@ class cplBCType bool useSvZeroD = false; // Whether to use svOneD (svOneDSolver) - bool useSv1D = false; + bool useSvOneD = false; // Whether to initialize RCR from flow data bool initRCR = false; @@ -884,9 +885,11 @@ class cplBCType std::string commuName; //std::string commuName = ".CPLBC_0D_3D.tmp"; - svZeroDSolverInterfaceType svzerod_solver_interface; + /// @brief Data structure used for coupling with svZeroD code + svZeroDSolverInterfaceData svzerod_solver_interface; - svOneDSolverInterfaceType sv1d_solver_interface; + /// @brief Data structure used for coupling with svOneD code + svOneDSolverInterfaceData svOneD_solver_interface; /// @brief The name of history file containing "X" std::string saveName; diff --git a/Code/Source/solver/CoupledBoundaryCondition.cpp b/Code/Source/solver/CoupledBoundaryCondition.cpp index 56f917afa..2d2ed353d 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.cpp +++ b/Code/Source/solver/CoupledBoundaryCondition.cpp @@ -937,27 +937,8 @@ CappingSurface::CappingSurface(const CappingSurface& other) , valM_(other.valM_) , normals_(other.normals_) { - if (other.face_ != nullptr) { - try { - face_ = std::make_unique(*other.face_); - } catch (const std::exception& e) { - throw CappingSurfaceCopyException("[CappingSurface::copy constructor] Failed to copy face_: " + - std::string(e.what())); - } - if (face_ != nullptr) { - if (face_->nNo > 0 && face_->gN.size() != face_->nNo) { - throw CappingSurfaceCopyException("[CappingSurface::copy constructor] Invalid face_: gN.size()=" + - std::to_string(face_->gN.size()) + " != nNo=" + - std::to_string(face_->nNo)); - } - if (face_->nEl > 0 && face_->IEN.ncols() != face_->nEl) { - throw CappingSurfaceCopyException("[CappingSurface::copy constructor] Invalid face_: IEN.ncols()=" + - std::to_string(face_->IEN.ncols()) + " != nEl=" + - std::to_string(face_->nEl)); - } - } - } else { - face_.reset(); + if (other.face_) { + face_ = std::make_unique(*other.face_); } } @@ -967,13 +948,8 @@ CappingSurface& CappingSurface::operator=(const CappingSurface& other) global_node_ids_ = other.global_node_ids_; valM_ = other.valM_; normals_ = other.normals_; - if (other.face_ != nullptr) { - try { - face_ = std::make_unique(*other.face_); - } catch (const std::exception& e) { - throw CappingSurfaceCopyException("[CappingSurface::operator=] Failed to copy face_: " + - std::string(e.what())); - } + if (other.face_) { + face_ = std::make_unique(*other.face_); } else { face_.reset(); } @@ -1120,10 +1096,6 @@ Array CappingSurface::update_element_position_global(int e, consts::Mech { using namespace consts; - if (mesh_x.nrows() < cap_nsd_ || mesh_Do.nrows() < cap_nsd_ || mesh_Dn.nrows() < cap_nsd_) { - throw CappingSurfaceGeometryException( - "[CappingSurface::update_element_position_global] Mesh arrays must have at least 3 rows."); - } Array xl(cap_nsd_, face_->eNoN); for (int a = 0; a < face_->eNoN; a++) { @@ -1153,13 +1125,6 @@ Array CappingSurface::update_element_position_global(int e, consts::Mech /// @return The Jacobian and normal vector. std::pair> CappingSurface::compute_jacobian_and_normal(const Array& xl, int e, int g) const { - if (xl.nrows() != cap_nsd_ || xl.ncols() != face_->eNoN) { - throw CappingSurfaceGeometryException("[CappingSurface::compute_jacobian_and_normal] xl has wrong dimensions: " + - std::to_string(xl.nrows()) + "x" + std::to_string(xl.ncols()) + - " (expected " + std::to_string(cap_nsd_) + "x" + - std::to_string(face_->eNoN) + ")."); - } - // Get the shape function derivatives for the Gauss point. Array Nx_g = face_->Nx.rslice(g); Array xXi(cap_nsd_, cap_insd_); @@ -1189,11 +1154,6 @@ std::pair> CappingSurface::compute_jacobian_and_normal(co // Check if the initial normals are provided and if they are valid. if (normals_.ncols() > 0 && normals_.nrows() == cap_nsd_) { - if (e < 0 || e >= normals_.ncols()) { - throw CappingSurfaceGeometryException("[CappingSurface::compute_jacobian_and_normal] Element index e=" + - std::to_string(e) + " is out of bounds for normals_ (ncols=" + - std::to_string(normals_.ncols()) + ")."); - } Vector n0(cap_nsd_); for (int i = 0; i < cap_nsd_; i++) { @@ -1277,19 +1237,7 @@ void CappingSurface::compute_valM(consts::MechanicalConfigurationType cfg, const auto [Jac, n] = compute_jacobian_and_normal(xl, e, g); for (int a = 0; a < face_->eNoN; a++) { int gnNo_idx = face_->IEN(a, e); - auto it = gnNo_to_cap_local.find(gnNo_idx); - if (it == gnNo_to_cap_local.end()) { - throw CappingSurfaceAssemblyException("[CappingSurface::compute_valM] IEN entry (element " + - std::to_string(e) + ", node " + std::to_string(a) + - ") contains invalid gnNo index " + std::to_string(gnNo_idx) + - " not found in cap face nodes."); - } - int cap_a = it->second; - if (cap_a < 0 || cap_a >= cap_nNo) { - throw CappingSurfaceAssemblyException("[CappingSurface::compute_valM] Invalid cap face-local index cap_a=" + - std::to_string(cap_a) + " (cap_nNo=" + std::to_string(cap_nNo) + - ")"); - } + int cap_a = gnNo_to_cap_local.at(gnNo_idx); for (int i = 0; i < cap_nsd_; i++) { valM_(i, cap_a) += face_->N(a, g) * face_->w(g) * Jac * n(i); } diff --git a/Code/Source/solver/CoupledBoundaryCondition.h b/Code/Source/solver/CoupledBoundaryCondition.h index 764877096..858354a3f 100644 --- a/Code/Source/solver/CoupledBoundaryCondition.h +++ b/Code/Source/solver/CoupledBoundaryCondition.h @@ -152,7 +152,7 @@ class CappingSurface { double integrate_velocity_flux(const CapGlobalMeshState& st, bool use_Yn_velocity, consts::MechanicalConfigurationType cfg); - /// @brief Compute the cap contribution to the linear solver face (fills \ref valM_; safe under \c const *this). + /// @brief Compute the cap contribution to the linear solver face (fills \ref valM_; safe under \c const *this). void compute_valM(consts::MechanicalConfigurationType cfg, const CapGlobalMeshState& st) const; /// @brief Get the cap face. @@ -210,11 +210,11 @@ class CoupledBoundaryCondition { std::string face_name_; ///< Face name from the mesh /// @brief svOneD coupling data std::string oned_input_file_; ///< Path to svOneDSolver input file (empty for svZeroD BCs) - /// @brief Pressure ramp parameters for 1D coupling initialization (DIR coupling only). + /// @brief Pressure ramp parameters for 1D coupling initialization. int oned_ramp_steps_ = 0; ///< Number of ramp steps (0 = disabled) - double oned_ramp_ref_pressure_ = 0.0; ///< Reference pressure at step 0 + double oned_ramp_ref_pressure_ = 0.0; ///< Reference pressure at step used for ramping pressure - /// @brief Under-relaxation factor for pressure passed to the 1D solver (DIR coupling only). + /// @brief Under-relaxation factor for pressure or flowrate passed to the 1D solver. /// Applied as: P_sent = omega * P_new + (1 - omega) * P_prev_sent. /// Range: (0, 1]. Default 1.0 = no relaxation. double oned_relax_factor_ = 1.0; @@ -333,7 +333,7 @@ class CoupledBoundaryCondition { /// @brief Set the svOneD input file path void set_oned_input_file(const std::string& path); - bool is_sv1d_face() const { return !oned_input_file_.empty(); } + bool is_svOneD_face() const { return !oned_input_file_.empty(); } /// @brief Get the pressure ramp step count (0 = disabled). int get_oned_ramp_steps() const { return oned_ramp_steps_; } diff --git a/Code/Source/solver/Parameters.h b/Code/Source/solver/Parameters.h index 0526ba3fd..29811616f 100644 --- a/Code/Source/solver/Parameters.h +++ b/Code/Source/solver/Parameters.h @@ -720,6 +720,7 @@ class svOneDSolverInterfaceParameters : public ParameterLists bool value_set = false; }; +/// @brief Body force over a mesh using the "Add_BF" command. /// /// \code {.xml} /// @@ -785,10 +786,12 @@ class BoundaryConditionRCRParameters : public ParameterLists /// @brief svZeroDSolver coupling options under Add_BC (with Time_dependence Coupled). /// /// \code {.xml} -/// -/// LV_IN -/// mesh/mesh-surfaces/endo_cap.vtp -/// +// +// OneDfilename.in +// 100 +// 0.0 +// 0.3 +// /// \endcode class CouplingInterfaceParameters : public ParameterLists { @@ -810,7 +813,7 @@ class CouplingInterfaceParameters : public ParameterLists // Over the first Coupling_ramp_steps committed time steps the value passed // to the 1D solver is linearly ramped: // DIR: pressure P ramped from Coupling_ramp_ref_pressure to actual 3D P. - // NEU: flow rate Q ramped from 0 to actual 3D Q (ref pressure not used). + // NEU: output pressure P is ramped before being applied to the 3D domain. // Set Coupling_ramp_steps = 0 (default) to disable. Parameter coupling_ramp_steps; Parameter coupling_ramp_ref_pressure; diff --git a/Code/Source/solver/baf_ini.cpp b/Code/Source/solver/baf_ini.cpp index fa07f881b..63bf7aaa8 100644 --- a/Code/Source/solver/baf_ini.cpp +++ b/Code/Source/solver/baf_ini.cpp @@ -12,7 +12,7 @@ #include "set_bc.h" #include "utils.h" #include "svZeroD_interface.h" -#include "svOneD_subroutines.h" +#include "svOneD_interface.h" #include "fsils_api.hpp" #include "fils_struct.hpp" @@ -123,7 +123,6 @@ void baf_ini(Simulation* simulation, SolutionStates& solutions) com_mod.cplBC.fa[i].name = com_mod.msh[iM].fa[iFa].name; com_mod.cplBC.fa[i].y = 0.0; - // Copy per-face 1D input file if present (svOneD coupling). if (!bc.oned_input_file.empty()) { com_mod.cplBC.fa[i].oned_input_file = bc.oned_input_file; } @@ -171,7 +170,7 @@ void baf_ini(Simulation* simulation, SolutionStates& solutions) svZeroD::init_svZeroD(com_mod, cm_mod); } - if (com_mod.cplBC.useSv1D) { + if (com_mod.cplBC.useSvOneD) { svOneD::init_svOneD(com_mod, cm_mod); } @@ -789,8 +788,8 @@ void fsi_ls_ini(ComMod& com_mod, const CmMod& cm_mod, bcType& lBc, const faceTyp sVl = 0.0; fsils_bc_create(com_mod.lhs, lsPtr, lFa.nNo, nsd, BcType::BC_TYPE_Dir, gNodes, sVl); - // Compute integral of normal vector over the face (needed for resistance BC/0D-coupling) } else if (btest(lBc.bType, iBC_res)) { + // Compute integral of normal vector over the face (needed for resistance BC/0D-coupling) sV = 0.0; for (int e = 0; e < lFa.nEl; e++) { if (lFa.eType == ElementType::NRB) { diff --git a/Code/Source/solver/distribute.cpp b/Code/Source/solver/distribute.cpp index 719bb3887..a2c7bd1c3 100644 --- a/Code/Source/solver/distribute.cpp +++ b/Code/Source/solver/distribute.cpp @@ -550,7 +550,7 @@ void distribute(Simulation* simulation) cm.bcast_enum(cm_mod, &cplBC.schm); cm.bcast(cm_mod, &cplBC.useGenBC); cm.bcast(cm_mod, &cplBC.useSvZeroD); - cm.bcast(cm_mod, &cplBC.useSv1D); + cm.bcast(cm_mod, &cplBC.useSvOneD); if (cplBC.useGenBC) { if (cm.slv(cm_mod)) { @@ -559,13 +559,13 @@ void distribute(Simulation* simulation) } } - if (cplBC.useSv1D) { - // Broadcast the sv1D solver interface data so that ALL ranks can call + if (cplBC.useSvOneD) { + // Broadcast the svOneD solver interface data so that ALL ranks can call // init_svOneD / calc_svOneD (which use MPI_Bcast collectives that require // every rank to participate). Without this, slave processes have // has_data = false and throw immediately inside init_svOneD. - cm.bcast(cm_mod, &cplBC.sv1d_solver_interface.has_data); - cm.bcast(cm_mod, cplBC.sv1d_solver_interface.solver_library); + cm.bcast(cm_mod, &cplBC.svOneD_solver_interface.has_data); + cm.bcast(cm_mod, cplBC.svOneD_solver_interface.solver_library); } if (!cplBC.useGenBC) { diff --git a/Code/Source/solver/read_files.cpp b/Code/Source/solver/read_files.cpp index 8f17483c8..6b574e5af 100644 --- a/Code/Source/solver/read_files.cpp +++ b/Code/Source/solver/read_files.cpp @@ -25,6 +25,7 @@ #include #include #include +#include "Core/Exception.h" namespace read_files_ns { @@ -251,34 +252,39 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, // Coupling to a 0D/1D model: // - GenBC / cplBC: Time_dependence Coupled without (cplBC.fa). // - svZeroDSolver: Time_dependence Coupled + (CoupledBoundaryCondition). + // - svOneDSolver: Time_dependence Coupled + (CoupledBoundaryCondition). // } else if (ctmp == "Coupled") { auto& face_name = com_mod.msh[lBc.iM].fa[lBc.iFa].name; const bool svzd_iface = com_mod.cplBC.svzerod_solver_interface.has_data; - const bool sv1d_iface = com_mod.cplBC.sv1d_solver_interface.has_data; + const bool svOneD_iface = com_mod.cplBC.svOneD_solver_interface.has_data; const bool ci_set = bc_params->coupling_interface.value_set; const bool ci_has_block = ci_set && bc_params->coupling_interface.svzerod_solver_block.defined(); const bool ci_has_1d_file = ci_set && bc_params->coupling_interface.svoned_input_file.defined(); // Sanity check: must define - if (svzd_iface || sv1d_iface) { + if (svzd_iface || svOneD_iface) { // svZeroD and/or svOneD path: route each face individually based on its // content. if (ci_has_block && ci_has_1d_file) { - throw std::runtime_error( - std::string("[read_bc] on face '") + face_name + - "' defines both and . " - "Specify exactly one per face."); + svmp::raise( + SVMP_HERE, + std::string("[read_bc] on face '") + face_name + + "' defines both and . " + "Specify exactly one per face.", + svmp::StatusCode::InvalidArgument); } if (ci_has_block) { // 0D face: route to svZeroD. if (!svzd_iface) { - throw std::runtime_error( + svmp::raise( + SVMP_HERE, std::string("[read_bc] Face '") + face_name + "' specifies but no " - "is defined on the equation."); + "is defined on the equation.", + svmp::StatusCode::InvalidArgument); } lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_Coupled)); lBc.bType = utils::ibclr(lBc.bType, enum_int(BoundaryConditionType::bType_Dir)); @@ -290,11 +296,13 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, } else if (ci_has_1d_file) { // 1D face: route to svOneD. - if (!sv1d_iface) { - throw std::runtime_error( + if (!svOneD_iface) { + svmp::raise( + SVMP_HERE, std::string("[read_bc] Face '") + face_name + "' specifies but no " - "is defined on the equation."); + "is defined on the equation.", + svmp::StatusCode::InvalidArgument); } lBc.oned_input_file = bc_params->coupling_interface.svoned_input_file.value(); @@ -307,41 +315,51 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, // cplBCptr stays -1; coupled_bc is constructed in the block below. if (com_mod.cplBC.schm == CplBCType::cplBC_NA) { - throw std::runtime_error( + svmp::raise( + SVMP_HERE, std::string("[read_bc] A coupling method (e.g. svOneDSolver_interface coupling_type) " "must be defined for Time_dependence Coupled on face '") + - face_name + "'."); + face_name + "'.", + svmp::StatusCode::InvalidArgument); } } else { // is present but has neither field, or is absent entirely. if (ci_set) { - throw std::runtime_error( + svmp::raise( + SVMP_HERE, std::string("[read_bc] on face '") + face_name + "' must define either (for 0D coupling) or " - " (for 1D coupling)."); + " (for 1D coupling).", + svmp::StatusCode::InvalidArgument); } if (svzd_iface) { - throw std::runtime_error( + svmp::raise( + SVMP_HERE, std::string("[read_bc] With , each svZeroD-coupled face needs " " with " "(Time_dependence Coupled) on face '") + - face_name + "'."); + face_name + "'.", + svmp::StatusCode::InvalidArgument); } else { - throw std::runtime_error( + svmp::raise( + SVMP_HERE, std::string("[read_bc] With , each 1D-coupled face needs " " with " "(Time_dependence Coupled) on face '") + - face_name + "'."); + face_name + "'.", + svmp::StatusCode::InvalidArgument); } } } else { // genBC / cplBC path: no svZeroD or svOneD interface defined. if (bc_params->coupling_interface.value_set) { - throw std::runtime_error( + svmp::raise( + SVMP_HERE, "[read_bc] is only valid when or " - " is defined on the equation."); + " is defined on the equation.", + svmp::StatusCode::InvalidArgument); } lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_cpl)); @@ -349,25 +367,36 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, lBc.cplBCptr = com_mod.cplBC.nFa - 1; if (com_mod.cplBC.schm == CplBCType::cplBC_NA) { - throw std::runtime_error( + svmp::raise( + SVMP_HERE, std::string("[read_bc] A coupling method (e.g. Couple_to_genBC) must be defined for Time_dependence " "Coupled on face '") + - face_name + "'."); + face_name + "'.", + svmp::StatusCode::InvalidArgument); } } } else if (ctmp == "Resistance") { lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_res)); if (!utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Neu))) { - throw std::runtime_error("[read_bc] Resistance is only defined for Neu BC."); + svmp::raise( + SVMP_HERE, + "[read_bc] Resistance is only defined for Neu BC.", + svmp::StatusCode::InvalidArgument); } if (utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Robin))) { - throw std::runtime_error("[read_bc] Resistance is not defined for Robin BC."); + svmp::raise( + SVMP_HERE, + "[read_bc] Resistance is not defined for Robin BC.", + svmp::StatusCode::InvalidArgument); } if (std::set{Equation_fluid,Equation_FSI,Equation_CMM}.count(lEq.phys) == 0) { - throw std::runtime_error("[read_bc] Resistance is only defined for fluid/CMM/SI equations."); + svmp::raise( + SVMP_HERE, + "[read_bc] Resistance is only defined for fluid/CMM/SI equations.", + svmp::StatusCode::InvalidArgument); } lBc.r = bc_params->value.value(); @@ -392,9 +421,9 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, lBc.RCR.Pd = bc_params->rcr.distal_pressure.value(); lBc.RCR.Xo = bc_params->rcr.initial_pressure.value(); - if ((com_mod.cplBC.schm != CplBCType::cplBC_NA && !com_mod.cplBC.useSv1D && !com_mod.cplBC.useSvZeroD) || + if ((com_mod.cplBC.schm != CplBCType::cplBC_NA && !com_mod.cplBC.useSvOneD && !com_mod.cplBC.useSvZeroD) || com_mod.cplBC.xo.size() != 0) { - throw std::runtime_error("[read_bc] RCR cannot be used in conjunction with cplBC (except alongside svOneD or svZeroD)."); + throw std::runtime_error("[read_bc] An RCR boundary condition can only be used when coupled with the svOneDsolver."); } com_mod.cplBC.nFa = com_mod.cplBC.nFa + 1; lBc.cplBCptr = com_mod.cplBC.nFa - 1; @@ -470,6 +499,7 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, // Coupled BC (svZeroDSolver or svOneDSolver via CoupledBoundaryCondition) + bool is_coupled_dir = false; if (utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Coupled))) { // Get associated face name @@ -483,9 +513,9 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, face_name + "'."); } - const bool is_sv1d_face = !lBc.oned_input_file.empty(); + const bool is_svOneD_face = !lBc.oned_input_file.empty(); - if (is_sv1d_face) { + if (is_svOneD_face) { // ------------------------------------------------------------------ // svOneD path: construct CoupledBoundaryCondition with empty // block_name and store the 1D input file path. @@ -522,23 +552,6 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, } } else if (com_mod.cplBC.svzerod_solver_interface.has_data) { - // ------------------------------------------------------------------ - // svZeroD path (existing). - // ------------------------------------------------------------------ - - // Sanity check: must be defined - if (!bc_params->coupling_interface.value_set) { - throw std::runtime_error( - std::string("[read_bc] CoupledBoundaryCondition requires for face '") + face_name + - "'."); - } - - // Sanity check: must be defined - if (!bc_params->coupling_interface.svzerod_solver_block.defined()) { - throw std::runtime_error( - std::string("[read_bc] must define for face '") + face_name + - "'."); - } // Get block name const std::string zd_block = bc_params->coupling_interface.svzerod_solver_block.value(); @@ -604,9 +617,19 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, std::string("[read_bc] bType_Coupled is set on face '") + face_name + "' but neither svZeroDSolver_interface nor svOneDSolver_interface is defined."); } - } + // For DIR Coupled BCs (svZeroD or svOneD), the downstream solver always returns a + // volumetric flow rate Q [m³/s], not a velocity [m/s]. Without bType_flx, bc_ini + // sets gx(a) = 1, and set_bc_dir_l applies velocity = Q * nV which has the wrong + // dimensions. With bType_flx, bc_ini normalises gx(a) = 1/area, so the applied + // velocity = (Q/area) * nV [m/s] is physically correct. Enforce this automatically + // regardless of the user's setting. + if (coupled_bc_type == BoundaryConditionType::bType_Dir) { + lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_flx)); + is_coupled_dir = true; + } + } // To impose value or flux bool ltmp = bc_params->impose_flux.value(); @@ -614,17 +637,6 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_flx)); } - // For DIR Coupled BCs (svZeroD or svOneD), the downstream solver always returns a - // volumetric flow rate Q [m³/s], not a velocity [m/s]. Without bType_flx, bc_ini - // sets gx(a) = 1, and set_bc_dir_l applies velocity = Q * nV which has the wrong - // dimensions. With bType_flx, bc_ini normalises gx(a) = 1/area, so the applied - // velocity = (Q/area) * nV [m/s] is physically correct. Enforce this automatically - // regardless of the user's setting. - if (utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Coupled)) && - coupled_bc_type == BoundaryConditionType::bType_Dir) { - lBc.bType = utils::ibset(lBc.bType, enum_int(BoundaryConditionType::bType_flx)); - } - // To zero-out perimeter or not. Default is .true. for Dir/CMM and Coupled-DIR. // For Coupled-DIR BCs, bType_Dir is cleared, but we still need bType_zp so that // bc_ini zeros shared perimeter nodes before normalising gx. Without bType_zp, @@ -632,16 +644,15 @@ void read_bc(Simulation* simulation, EquationParameters* eq_params, eqType& lEq, // effective flux to Q*(interior_area/full_area) instead of Q. // ltmp = false; - ltmp = utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Dir)); - // Also enable zero-perimeter for Coupled-DIR (svZeroD or svOneD DIR coupling). - if (!ltmp && - utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Coupled)) && - coupled_bc_type == BoundaryConditionType::bType_Dir) { - ltmp = true; - } + ltmp = utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_Dir)) || is_coupled_dir; + if (bc_params->zero_out_perimeter.defined()) { ltmp = bc_params->zero_out_perimeter.value(); } + + if (is_coupled_dir) { + ltmp = true; + } lBc.bType = utils::ibclr(lBc.bType, enum_int(BoundaryConditionType::bType_zp)); if (ltmp || utils::btest(lBc.bType, enum_int(BoundaryConditionType::bType_CMM))) { @@ -1604,18 +1615,18 @@ void read_eq(Simulation* simulation, EquationParameters* eq_params, eqType& lEq) } if (eq_params->svonedsolver_interface_parameters.defined()) { - const std::string sv1d_type = eq_params->svonedsolver_interface_parameters.coupling_type.value(); + const std::string svOneD_type = eq_params->svonedsolver_interface_parameters.coupling_type.value(); // When both interfaces are defined, their Coupling_type must match. - if (!cplbc_type_str.empty() && cplbc_type_str != sv1d_type) { + if (!cplbc_type_str.empty() && cplbc_type_str != svOneD_type) { throw std::runtime_error( "[read_eq] svZeroDSolver_interface and svOneDSolver_interface must use the same " "Coupling_type in a mixed-coupling simulation " "(svZeroDSolver_interface has '" + cplbc_type_str + - "', svOneDSolver_interface has '" + sv1d_type + "')."); + "', svOneDSolver_interface has '" + svOneD_type + "')."); } - cplbc_type_str = sv1d_type; - cplBC.useSv1D = true; - cplBC.sv1d_solver_interface.set_data(eq_params->svonedsolver_interface_parameters); + cplbc_type_str = svOneD_type; + cplBC.useSvOneD = true; + cplBC.svOneD_solver_interface.set_data(eq_params->svonedsolver_interface_parameters); } } @@ -1729,7 +1740,7 @@ void read_eq(Simulation* simulation, EquationParameters* eq_params, eqType& lEq) } // Only set coupling scheme if not already configured by an external solver (e.g. svOneD, svZeroD). // When svOneD/svZeroD and RCR coexist, the external solver owns the scheme; RCR uses the same scheme. - if (!cplBC.useSv1D && !cplBC.useSvZeroD) { + if (!cplBC.useSvOneD && !cplBC.useSvZeroD) { cplBC.schm = CplBCType::cplBC_SI; if (lEq.useTLS) { cplBC.schm = CplBCType::cplBC_E; diff --git a/Code/Source/solver/set_bc.cpp b/Code/Source/solver/set_bc.cpp index 35673ca27..704030bb0 100644 --- a/Code/Source/solver/set_bc.cpp +++ b/Code/Source/solver/set_bc.cpp @@ -18,7 +18,7 @@ #include "utils.h" #include #include "svZeroD_interface.h" -#include "svOneD_subroutines.h" +#include "svOneD_interface.h" namespace set_bc { @@ -180,15 +180,15 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& if (cplBC.useGenBC) { set_bc::genBC_Integ_X(com_mod, cm_mod, "D"); } else { - // In mixed-coupling simulations both useSvZeroD and useSv1D can be true. + // In mixed-coupling simulations both useSvZeroD and useSvOneD can be true. // Call each active solver independently. if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); } - if (cplBC.useSv1D) { + if (cplBC.useSvOneD) { svOneD::calc_svOneD(com_mod, cm_mod, 'D'); } - if (!cplBC.useSvZeroD && !cplBC.useSv1D) { + if (!cplBC.useSvZeroD && !cplBC.useSvOneD) { set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); } else if (RCRflag) { // Also integrate any RCR faces that coexist with svZeroD/svOneD faces. @@ -244,7 +244,7 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& if (RCRflag) { set_bc::cplBC_Integ_X(com_mod, cm_mod, true); } - } else if (cplBC.useSv1D) { + } else if (cplBC.useSvOneD) { svOneD::calc_svOneD(com_mod, cm_mod, 'D'); // Also integrate any RCR faces that coexist with svOneD faces. if (RCRflag) { @@ -291,7 +291,7 @@ void calc_der_cpl_bc(ComMod& com_mod, const CmMod& cm_mod, const SolutionStates& // Perturb flowrate and compute new pressure bc.coupled_bc.perturb_flowrate(diff); - if (bc.coupled_bc.is_sv1d_face()) { + if (bc.coupled_bc.is_svOneD_face()) { svOneD::calc_svOneD(com_mod, cm_mod, 'D'); } else { svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); @@ -847,15 +847,15 @@ void set_bc_cpl(ComMod& com_mod, CmMod& cm_mod, const SolutionStates& solutions) if (cplBC.useGenBC) { set_bc::genBC_Integ_X(com_mod, cm_mod, "D"); } else { - // In mixed-coupling simulations both useSvZeroD and useSv1D can be true. + // In mixed-coupling simulations both useSvZeroD and useSvOneD can be true. // Call each active solver independently. if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'D'); } - if (cplBC.useSv1D) { + if (cplBC.useSvOneD) { svOneD::calc_svOneD(com_mod, cm_mod, 'D'); } - if (!cplBC.useSvZeroD && !cplBC.useSv1D) { + if (!cplBC.useSvZeroD && !cplBC.useSvOneD) { set_bc::cplBC_Integ_X(com_mod, cm_mod, RCRflag); } else if (RCRflag) { // Also integrate any RCR faces that coexist with svZeroD/svOneD faces. diff --git a/Code/Source/solver/svOneD_subroutines.cpp b/Code/Source/solver/svOneD_interface.cpp similarity index 92% rename from Code/Source/solver/svOneD_subroutines.cpp rename to Code/Source/solver/svOneD_interface.cpp index 8789617a8..294ec0b73 100644 --- a/Code/Source/solver/svOneD_subroutines.cpp +++ b/Code/Source/solver/svOneD_interface.cpp @@ -38,7 +38,7 @@ // // Time-stepping (calc_svOneD): // Phase 1 – parallel solve: -// - Each rank runs run_step() for its owned model(s) with no MPI +// - Each rank runs run_simulation() for its owned model(s) with no MPI // calls, so model k on rank A and model k+1 on rank B truly run // concurrently. // Phase 2 – batch result exchange: @@ -53,7 +53,7 @@ // params[3] = BC_val_old (Q or P at t_old) // params[4] = BC_val_new (Q or P at t_new) -#include "svOneD_subroutines.h" +#include "svOneD_interface.h" #include #include @@ -61,11 +61,13 @@ #include #include #include +#include #include "ComMod.h" #include "consts.h" #include "utils.h" #include "svOneD_interface/OneDSolverInterface.h" +#include "Core/Exception.h" #include "mpi.h" @@ -149,8 +151,13 @@ static double svOneDTime = 0.0; // --------------------------------------------------------------------------- static std::string resolve_lib_path(const std::string& lib_base) { - if (std::ifstream(lib_base + ".so").good()) return lib_base + ".so"; - if (std::ifstream(lib_base + ".dylib").good()) return lib_base + ".dylib"; + if (std::filesystem::is_regular_file(lib_base + ".so")) { + return lib_base + ".so"; + } + + if (std::filesystem::is_regular_file(lib_base + ".dylib")) { + return lib_base + ".dylib"; + } return lib_base; // already has extension, or will fail at dlopen time } @@ -162,13 +169,18 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) using namespace consts; auto& cplBC = com_mod.cplBC; - auto& solver_if = cplBC.sv1d_solver_interface; + auto& solver_if = cplBC.svOneD_solver_interface; auto& cm = com_mod.cm; const int nProcs = cm.nProcs; const int myRank = cm.taskId; if (!solver_if.has_data) { - throw std::runtime_error("[svOneD::init_svOneD] svOneD solver interface data is missing."); + throw svmp::CoreException( + "[svOneD::init_svOneD] svOneD solver interface data is missing.", + svmp::StatusCode::InvalidState, + __FILE__, + __LINE__, + __func__); } // Initialize the 1D simulation clock from the 3D solver's current time so @@ -197,7 +209,12 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) } if (oned_models.empty()) { - throw std::runtime_error("[svOneD::init_svOneD] No svOneD-coupled faces with input files found."); + throw svmp::CoreException( + "[svOneD::init_svOneD] No svOneD-coupled faces with input files found.", + svmp::StatusCode::InvalidState, + __FILE__, + __LINE__, + __func__); } // ----- Guard: require at least one MPI rank per 1D model ----- @@ -206,12 +223,16 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) // more than once, corrupting the static problem-ID state inside the shared library. const int nTotalModels = static_cast(oned_models.size()); if (nProcs < nTotalModels) { - throw std::runtime_error( - "[svOneD::init_svOneD] Number of MPI processes (" + std::to_string(nProcs) + - ") is less than the number of svOneD-coupled faces (" + - std::to_string(nTotalModels) + - "). Please run with at least " + std::to_string(nTotalModels) + - " MPI processes."); + throw svmp::CoreException( + "[svOneD::init_svOneD] Number of MPI processes (" + std::to_string(nProcs) + + ") is less than the number of svOneD-coupled faces (" + + std::to_string(nTotalModels) + + "). Please run with at least " + std::to_string(nTotalModels) + + " MPI processes.", + svmp::StatusCode::InvalidState, + __FILE__, + __LINE__, + __func__); } // ----- Load shared library (once per process) ----- @@ -345,12 +366,13 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) int save_incr = com_mod.saveIncr; int error_code = 0; - st.interface->run_step(st.problem_id, t_old, save_incr, - st.coupling_type, params, - work_sol.data(), cpl_values[k], BCFlag, error_code); + st.interface->run_simulation(st.problem_id, t_old, save_incr, + st.coupling_type, params, + work_sol.data(), cpl_values[k], BCFlag, error_code); if (error_code != 0) { - throw std::runtime_error( + svmp::raise( + SVMP_HERE, "[svOneD::calc_svOneD] 1D solver step for face '" + bc.coupled_bc.get_oned_input_file() + "' failed with error code " + std::to_string(error_code)); diff --git a/Code/Source/solver/svOneD_subroutines.h b/Code/Source/solver/svOneD_interface.h similarity index 100% rename from Code/Source/solver/svOneD_subroutines.h rename to Code/Source/solver/svOneD_interface.h diff --git a/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp b/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp index da6e098c2..b0fb451be 100644 --- a/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp +++ b/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "Core/Exception.h" OneDSolverInterface::~OneDSolverInterface() { @@ -20,8 +21,13 @@ void OneDSolverInterface::load_library(const std::string& interface_lib) { library_handle_ = dlopen(interface_lib.c_str(), RTLD_LAZY); if (!library_handle_) { - throw std::runtime_error(std::string("[OneDSolverInterface] Could not load shared library '") + - interface_lib + "': " + dlerror()); + throw svmp::CoreException( + std::string("[OneDSolverInterface] Could not load shared library '") + + interface_lib + "': " + dlerror(), + svmp::StatusCode::DependencyError, + __FILE__, + __LINE__, + __func__); } // Clear any existing error. @@ -31,8 +37,13 @@ void OneDSolverInterface::load_library(const std::string& interface_lib) void* sym = dlsym(library_handle_, name); const char* err = dlerror(); if (err) { - throw std::runtime_error(std::string("[OneDSolverInterface] Could not load symbol '") + - name + "': " + err); + throw svmp::CoreException( + std::string("[OneDSolverInterface] Could not load symbol '") + + name + "': " + err, + svmp::StatusCode::DependencyError, + __FILE__, + __LINE__, + __func__); } return sym; }; @@ -51,7 +62,12 @@ void OneDSolverInterface::initialize(const std::string& input_file, const std::string& coupling_type) { if (!initialize_1d_) { - throw std::runtime_error("[OneDSolverInterface] initialize_1d not loaded"); + throw svmp::CoreException( + "[OneDSolverInterface] initialize_1d not loaded", + svmp::StatusCode::DependencyError, + __FILE__, + __LINE__, + __func__); } initialize_1d_(input_file.c_str(), problem_id, system_size, coupling_type.c_str()); @@ -62,7 +78,12 @@ void OneDSolverInterface::initialize(const std::string& input_file, void OneDSolverInterface::set_external_step_size(int problem_id, double dt) { if (!set_external_step_size_1d_) { - throw std::runtime_error("[OneDSolverInterface] set_external_step_size_1d not loaded"); + throw svmp::CoreException( + "[OneDSolverInterface] set_external_step_size_1d not loaded", + svmp::StatusCode::DependencyError, + __FILE__, + __LINE__, + __func__); } set_external_step_size_1d_(problem_id, dt); } @@ -70,7 +91,12 @@ void OneDSolverInterface::set_external_step_size(int problem_id, double dt) void OneDSolverInterface::return_solution(int problem_id, double* solution, int size) { if (!return_1d_solution_) { - throw std::runtime_error("[OneDSolverInterface] return_1d_solution not loaded"); + throw svmp::CoreException( + "[OneDSolverInterface] return_1d_solution not loaded", + svmp::StatusCode::DependencyError, + __FILE__, + __LINE__, + __func__); } return_1d_solution_(problem_id, solution, size); } @@ -78,12 +104,17 @@ void OneDSolverInterface::return_solution(int problem_id, double* solution, int void OneDSolverInterface::update_solution(int problem_id, double* solution, int size) { if (!update_1d_solution_) { - throw std::runtime_error("[OneDSolverInterface] update_1d_solution not loaded"); + throw svmp::CoreException( + "[OneDSolverInterface] update_1d_solution not loaded", + svmp::StatusCode::DependencyError, + __FILE__, + __LINE__, + __func__); } update_1d_solution_(problem_id, solution, size); } -void OneDSolverInterface::run_step(int problem_id, double current_time, +void OneDSolverInterface::run_simulation(int problem_id, double current_time, int save_incr, const std::string& coupling_type, double* params, double* solution, @@ -91,7 +122,12 @@ void OneDSolverInterface::run_step(int problem_id, double current_time, int& error_code) { if (!run_1d_simulation_step_1d_) { - throw std::runtime_error("[OneDSolverInterface] run_1d_simulation_step_1d not loaded"); + throw svmp::CoreException( + "[OneDSolverInterface] run_1d_simulation_step_1d not loaded", + svmp::StatusCode::DependencyError, + __FILE__, + __LINE__, + __func__); } // Copy coupling_type into a mutable buffer (shared-library uses char*). std::vector ctype_buf(coupling_type.begin(), coupling_type.end()); @@ -107,7 +143,12 @@ void OneDSolverInterface::extract_coupled_dof(int problem_id, int& coupled_dof, const std::string& coupling_type) { if (!extract_coupled_dof_) { - throw std::runtime_error("[OneDSolverInterface] extract_coupled_dof not loaded"); + throw svmp::CoreException( + "[OneDSolverInterface] extract_coupled_dof not loaded", + svmp::StatusCode::DependencyError, + __FILE__, + __LINE__, + __func__); } // Copy into a mutable buffer; the shared-library function signature uses // char* (not const char*) so we must pass a writable copy. diff --git a/Code/Source/solver/svOneD_interface/OneDSolverInterface.h b/Code/Source/solver/svOneD_interface/OneDSolverInterface.h index 29fcb3dd3..a13dd30d0 100644 --- a/Code/Source/solver/svOneD_interface/OneDSolverInterface.h +++ b/Code/Source/solver/svOneD_interface/OneDSolverInterface.h @@ -10,8 +10,19 @@ /// @brief Wrapper class for dynamically loading and calling the 1D solver shared library. /// -/// This class uses dlopen/dlsym to load the libsvoned_interface shared library at runtime -/// and provides C++ method wrappers for each of its exported C functions. +/// This class loads the svOneDSolver shared library and +/// provides a C++ wrapper around its exported C interface. +/// +/// The interface is used to couple 3D Navier-Stokes simulations in +/// svMultiPhysics with reduced-order 1D blood flow models in +/// svOneDSolver. +/// +/// Supported coupling modes: +/// - NEU coupling: 3D flow rate -> 1D model, 1D pressure -> 3D solver +/// - DIR coupling: 3D pressure -> 1D model, 1D flow rate -> 3D solver +/// +/// Each initialized 1D model is identified by a problem_id assigned +/// by svOneDSolver and used in subsequent library calls. /// /// Shared library functions: /// - initialize_1d(input_file, problem_id, system_size, coupling_type) @@ -31,10 +42,11 @@ class OneDSolverInterface { void load_library(const std::string& interface_lib); /// @brief Initialize the 1D solver from an input file. - /// @param input_file Path to the 1D solver .in file. - /// @param problem_id Output: problem identifier assigned by the solver. - /// @param system_size Output: total number of DOFs (nodes * 2: flow + area). - /// @param coupling_type "NEU" or "DIR" coupling direction. + /// + /// @param[in] input_file Path to the 1D solver .in file. + /// @param[out] problem_id Problem identifier assigned by the solver. + /// @param[out] system_size Total number of DOFs (nodes * 2: flow + area). + /// @param[in] coupling_type "NEU" or "DIR" coupling direction. void initialize(const std::string& input_file, int& problem_id, int& system_size, const std::string& coupling_type); @@ -48,19 +60,19 @@ class OneDSolverInterface { void update_solution(int problem_id, double* solution, int size); /// @brief Advance the 1D solver by one time step. - /// @param problem_id Problem identifier. - /// @param current_time Current simulation time (start of the step). - /// @param save_incr VTK output interval (Increment_in_saving_VTK_files from solver.xml); + /// + /// @param[in] problem_id Problem identifier. + /// @param[in] save_incr VTK output interval (Increment_in_saving_VTK_files from solver.xml); /// the 1D library decides internally whether to write output. - /// @param coupling_type "NEU" or "DIR". - /// @param params Array [N, t1, t2, ..., val1, val2, ...] where N=2. - /// @param solution In/out: solution vector updated after the step. - /// @param cpl_value Output: the BC value returned by the 1D solver + /// @param[in] coupling_type "NEU" or "DIR". + /// @param[in] params Array [N, t1, t2, ..., val1, val2, ...] where N=2. + /// @param[in,out] solution Solution vector updated after the step. + /// @param[out] cpl_value The BC value returned by the 1D solver /// (pressure for NEU, flow for DIR). - /// @param last_flag 'L' for the final (committed) iteration, 'D' for + /// @param[in] last_flag 'L' for the final (committed) iteration, 'D' for /// derivative / predictor steps. - /// @param error_code Output: non-zero on failure. - void run_step(int problem_id, double current_time, int save_incr, + /// @param[out] error_code Non-zero on failure. + void run_simulation(int problem_id, double current_time, int save_incr, const std::string& coupling_type, double* params, double* solution, double& cpl_value, char last_flag, int& error_code); @@ -70,20 +82,35 @@ class OneDSolverInterface { void extract_coupled_dof(int problem_id, int& coupled_dof, const std::string& coupling_type); - // Public data members set after initialize(). + + private: + /// @brief Problem identifier assigned by the 1D solver during initialization. int problem_id_ = 0; + /// @brief Total number of DOFs (nodes * 2: flow + area) assigned by the 1D solver during initialization. int system_size_ = 0; - private: void* library_handle_ = nullptr; // Function pointers to shared-library symbols. + + /// @brief Initialize the 1D solver void (*initialize_1d_)(const char*, int&, int&, const char*) = nullptr; + + /// @brief Set the 1D solver's internal time step size void (*set_external_step_size_1d_)(int, double) = nullptr; + + /// @brief Return the current 1D solution vector void (*return_1d_solution_)(int, double*, int) = nullptr; + + /// @brief Update the 1D solver's current solution vector void (*update_1d_solution_)(int, double*, int) = nullptr; + + /// @brief Advance the 1D solver by one 3D time step void (*run_1d_simulation_step_1d_)(int, double, int, const char*, double*, double*, double&, char*, int&) = nullptr; + + /// @brief Retrieve the index within the solution vector that corresponds to + /// the coupled boundary DOF. void (*extract_coupled_dof_)(int, int&, char*) = nullptr; }; diff --git a/Code/Source/solver/svZeroD_interface.cpp b/Code/Source/solver/svZeroD_interface.cpp index 6d9898736..2a3c8da8f 100644 --- a/Code/Source/solver/svZeroD_interface.cpp +++ b/Code/Source/solver/svZeroD_interface.cpp @@ -29,11 +29,11 @@ static void build_svzero_coupled_bc_idxs(ComMod& com_mod) for (int iBc = 0; iBc < com_mod.eq[iEq].nBc; iBc++) { const auto& bc = com_mod.eq[iEq].bc[iBc]; // A Coupled face belongs to svZeroD only when it is not a 1D face. - // The is_sv1d_face() helper encapsulates the routing invariant: + // The is_svOneD_face() helper encapsulates the routing invariant: // 1D face → oned_input_file_ non-empty, block_name_ empty // 0D face → block_name_ non-empty, oned_input_file_ empty if (utils::btest(bc.bType, consts::iBC_Coupled) && - !bc.coupled_bc.is_sv1d_face()) { + !bc.coupled_bc.is_svOneD_face()) { cpl.svZeroD_coupled_bc_idxs.emplace_back(iEq, iBc); } } @@ -463,7 +463,7 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) double Q_target_old, Q_target_new; if (ramp_steps > 0) { double ramp_factor = std::min(1.0, static_cast(bc->coupled_bc.get_ramp_step_count()) / ramp_steps); - double Q_ref = bc->coupled_bc.get_oned_ramp_ref_pressure(); // ref value (0.0 by default) + double Q_ref = 0.0; // ref value (0.0 by default) Q_target_old = Q_ref + ramp_factor * (raw_Q_old - Q_ref); Q_target_new = Q_ref + ramp_factor * (raw_Q_new - Q_ref); } else { diff --git a/Code/Source/solver/txt.cpp b/Code/Source/solver/txt.cpp index bbe6b033d..89f03c907 100644 --- a/Code/Source/solver/txt.cpp +++ b/Code/Source/solver/txt.cpp @@ -13,7 +13,7 @@ #include "utils.h" #include #include "svZeroD_interface.h" -#include "svOneD_subroutines.h" +#include "svOneD_interface.h" namespace txt_ns { @@ -175,14 +175,14 @@ void txt(Simulation* simulation, const bool init_write, const SolutionStates& so if (!init_write) { if (cplBC.useGenBC) { set_bc::genBC_Integ_X(com_mod, cm_mod, "L"); - } else { - // In mixed-coupling simulations both useSvZeroD and useSv1D can be true. + } else { + // In mixed-coupling simulations both useSvZeroD and useSvOneD can be true. // Call each active solver independently. if (cplBC.useSvZeroD) { svZeroD::calc_svZeroD(com_mod, cm_mod, 'L'); } - if (cplBC.useSv1D) { + if (cplBC.useSvOneD) { // Update NEU coupling flowrates from the final converged velocity // field (Yn) before committing the 1D solution. During the Newton // loop, compute_flowrates() is called at the *start* of each @@ -193,16 +193,16 @@ void txt(Simulation* simulation, const bool init_write, const SolutionStates& so // B_NS_Velocity_flux.txt. for (auto& bc : com_mod.eq[0].bc) { if (utils::btest(bc.bType, iBC_Coupled) && - bc.coupled_bc.is_sv1d_face() && + bc.coupled_bc.is_svOneD_face() && bc.coupled_bc.get_bc_type() == BoundaryConditionType::bType_Neu) { bc.coupled_bc.compute_flowrates(com_mod, cm_mod, solutions); } } - svOneD::calc_svOneD(com_mod, cm_mod, 'L'); + svOneD::calc_svOneD(com_mod, cm_mod, 'L'); } - // Also integrate any RCR faces coexisting with svZeroD/svOneD faces. - if (cplBC.useSvZeroD || cplBC.useSv1D) { + // Also integrate any RCR faces coexisting with svZeroD/svOneD faces. + if (cplBC.useSvZeroD || cplBC.useSvOneD) { for (auto& bc : com_mod.eq[0].bc) { if (utils::btest(bc.bType, iBC_RCR)) { ltmp = true; diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/inlet_1d.in b/tests/cases/fluid/Tpipe_svOneD_2faces/inlet_1d.in new file mode 100644 index 000000000..2bcf832c0 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/inlet_1d.in @@ -0,0 +1,125 @@ +# ================================ +# inlet_1d MODEL - UNITS IN CGS +# ================================ + +# ========== +# MODEL CARD +# ========== +# - Name of the model (string) + +MODEL inlet_1d + + + +### DO NOT CHANGE THIS SECTION - generated automatically +# +# ========== +# NODE CARD +# ========== +# - Node Name (double) +# - Node X Coordinate (double) +# - Node Y Coordinate (double) +# - Node Z Coordinate (double) + +NODE 0 -6.320068093537426e-18 5.27257554949756e-07 -10.0 +NODE 1 2.3869191998230702e-18 5.27257554949756e-07 0.0 + + +### DO NOT CHANGE THIS SECTION - generated automatically +# +# ========== +# JOINT CARD +# ========== +# - Joint Name (string) +# - Joint Node (double) +# - Joint Inlet Name (string) +# - Joint Outlet Name (string) + + + +### DO NOT CHANGE THIS SECTION - generated automatically +# +# ================================ +# JOINTINLET AND JOINTOUTLET CARDS +# ================================ +# - Inlet/Outlet Name (string) +# - Total Number of segments (int) +# - List of segments (list of int) + +# ============ +# SEGMENT CARD +# ============ +# - Segment Name (string) +# - Segment ID (int) +# - Segment Length (double) +# - Total Finite Elements in Segment (int) +# - Segment Inlet Node (int) +# - Segment Outlet Node (int) +# - Segment Inlet Area (double) +# - Segment Outlet Area (double) +# - Segment Inflow Value (double) +# - Segment Material (string) +# - Type of Loss (string - 'NONE','STENOSIS','BRANCH_THROUGH_DIVIDING','BRANCH_SIDE_DIVIDING','BRANCH_THROUGH_CONVERGING', +# 'BRANCH_SIDE_CONVERGING','BIFURCATION_BRANCH') +# - Branch Angle (double) +# - Upstream Segment ID (int) +# - Branch Segment ID (int) +# - Boundary Condition Type (string - 'NOBOUND','PRESSURE','AREA','FLOW','RESISTANCE','RESISTANCE_TIME','PRESSURE_WAVE', +# 'WAVE','RCR','CORONARY','IMPEDANCE','PULMONARY') +# - Data Table Name (string) + +SEGMENT branch0_seg0 0 10.0 20 0 1 3.130298366662493 3.1302983666630864 0.0 MAT1 NONE 0.0 0 0 COUPLED NONE + + + +DATATABLE INFLOW LIST +0.0 100.0 +10.0 100.0 +ENDDATATABLE + + +# ================== +# SOLVEROPTIONS CARD +# ================== +# - Solver Time Step (double), +# - Steps Between Saves (int), +# - Max Number of Steps (int) +# - Number of quadrature points for finite elements (int), +# - Name of Datatable for inlet conditions (string) +# - Type of boundary condition (string - 'NOBOUND','PRESSURE','AREA','FLOW','RESISTANCE','RESISTANCE_TIME','PRESSURE_WAVE', +# 'WAVE','RCR','CORONARY','IMPEDANCE','PULMONARY') +# - Convergence tolerance (double), +# - Formulation Type (int - 0 Advective, 1 Conservative), +# - Stabilization (int - 0 No stabilization, 1 With stabilization) + +SOLVEROPTIONS 0.005 20 100 2 INFLOW FLOW 1.0e-5 1 1 + +COUPLING ON DIR 1 + +# ============= +# MATERIAL CARD +# ============= +# - Material Name (string) +# - Material Type (string - 'LINEAR','OLUFSEN') +# - Material Density (double) +# - Material Viscosity (double) +# - Material PRef (double) +# - Material Exponent (double) +# - Material Parameter 1 (double) +# - Material Parameter 2 (double) +# - Material Parameter 3 (double) + +MATERIAL MAT1 LINEAR 1.06 0.04 0.0 1.0 10000000.0 0.0 0.0 + +# ============ +# OUTPUT CARD +# ============ +# +# 1. Output file format. The following output types are supported: +# TEXT. The output of every segment is written in separate text files for the flow rate, pressure, area and Reynolds number. The rows contain output values at varying locations along the segment while columns contains results at various time instants. +# VTK. The results for all time steps are plotted to a 3D-like model using the XML VTK file format. +# 2. VTK export option. Two options are available for VTK file outputs: +# 0 - Multiple files (default). A separate file is written for each saved increment. A pvd file is also provided which contains the time information of the sequence. This is the best option to create animations. +# 1 - The results for all time steps are plotted to a single XML VTK file. + +OUTPUT TEXT diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/inlet_1d_00050.vtp b/tests/cases/fluid/Tpipe_svOneD_2faces/inlet_1d_00050.vtp new file mode 100644 index 000000000..6114f8b75 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/inlet_1d_00050.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad8a9596cd4f77a927c217f02e2f540fe82e7a20927f1bb1457ff20d11fda578 +size 66000 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-complete.exterior.vtp b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-complete.exterior.vtp new file mode 100644 index 000000000..324c6ec2b --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-complete.exterior.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6d3ed0de2bbb08a7dc17388e1de4d7504096fbe5abe8d0bf88cab534353efda +size 119781 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-complete.mesh.vtu b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-complete.mesh.vtu new file mode 100644 index 000000000..58e35a91a --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-complete.mesh.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ba04aa43f6fcc96feb1c349840de4acac0329b6d3f388bf9ec17bc9ff8018ff +size 642824 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_main_3d.vtp b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_main_3d.vtp new file mode 100644 index 000000000..b98c01a7f --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_main_3d.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:732c605b92af8941d9cce53caf89928fb9de1876498539ad6be1ae6b4c814d69 +size 4910 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_main_3d_2.vtp b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_main_3d_2.vtp new file mode 100644 index 000000000..4efbbd59b --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_main_3d_2.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5724183e2c702cb93b14c8d35b10cccfbada94c2a54556cf35e3524faac76f35 +size 4828 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_side_3d.vtp b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_side_3d.vtp new file mode 100644 index 000000000..ee0e3e1cb --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/cap_side_3d.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bfc9a71ddd1b378205bd551f932db3da5f9945590aff9f80d28c294dd18c0f3 +size 3511 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/wall_main_3d.vtp b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/wall_main_3d.vtp new file mode 100644 index 000000000..5e0c767f5 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/wall_main_3d.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be46a91f418bd88cf8711de48e158b16937c9487278f6d4eafad395d30f68c35 +size 95467 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/wall_side_3d.vtp b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/wall_side_3d.vtp new file mode 100644 index 000000000..7a2f94350 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/mesh-surfaces/wall_side_3d.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56f42230ac103ace09256b6c49fa5213c1b7945d155565358816f50a17800163 +size 20913 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/walls_combined.vtp b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/walls_combined.vtp new file mode 100644 index 000000000..08ef317b4 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/mesh-complete/walls_combined.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c049bf671ca4bbc51054e32707305a879cf50fbdf5514d5de5aedadcda34c52 +size 113058 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/outlet_1d.in b/tests/cases/fluid/Tpipe_svOneD_2faces/outlet_1d.in new file mode 100644 index 000000000..1e6e03e71 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/outlet_1d.in @@ -0,0 +1,137 @@ +# ================================ +# main_1d MODEL - UNITS IN CGS +# ================================ + +# ========== +# MODEL CARD +# ========== +# - Name of the model (string) + +MODEL outlet_1d + + + +### DO NOT CHANGE THIS SECTION - generated automatically +# +# ========== +# NODE CARD +# ========== +# - Node Name (double) +# - Node X Coordinate (double) +# - Node Y Coordinate (double) +# - Node Z Coordinate (double) + +NODE 0 -6.320068093537426e-18 5.27257554949756e-07 22.520139694213867 +NODE 1 -6.72370548127077e-10 2.772360858216416e-05 27.36005401611328 +NODE 2 -0.0007925734389573336 1.7465894416091032e-05 28.852317810058594 +NODE 3 2.3869191998230702e-18 5.27257554949756e-07 30.0 + + +### DO NOT CHANGE THIS SECTION - generated automatically +# +# ========== +# JOINT CARD +# ========== +# - Joint Name (string) +# - Joint Node (double) +# - Joint Inlet Name (string) +# - Joint Outlet Name (string) + + + +### DO NOT CHANGE THIS SECTION - generated automatically +# +# ================================ +# JOINTINLET AND JOINTOUTLET CARDS +# ================================ +# - Inlet/Outlet Name (string) +# - Total Number of segments (int) +# - List of segments (list of int) + +JOINT J0 1 IN0 OUT0 +JOINTINLET IN0 1 0 +JOINTOUTLET OUT0 1 1 + +JOINT J1 2 IN1 OUT1 +JOINTINLET IN1 1 1 +JOINTOUTLET OUT1 1 2 + +# ============ +# SEGMENT CARD +# ============ +# - Segment Name (string) +# - Segment ID (int) +# - Segment Length (double) +# - Total Finite Elements in Segment (int) +# - Segment Inlet Node (int) +# - Segment Outlet Node (int) +# - Segment Inlet Area (double) +# - Segment Outlet Area (double) +# - Segment Inflow Value (double) +# - Segment Material (string) +# - Type of Loss (string - 'NONE','STENOSIS','BRANCH_THROUGH_DIVIDING','BRANCH_SIDE_DIVIDING','BRANCH_THROUGH_CONVERGING', +# 'BRANCH_SIDE_CONVERGING','BIFURCATION_BRANCH') +# - Branch Angle (double) +# - Upstream Segment ID (int) +# - Branch Segment ID (int) +# - Boundary Condition Type (string - 'NOBOUND','PRESSURE','AREA','FLOW','RESISTANCE','RESISTANCE_TIME','PRESSURE_WAVE', +# 'WAVE','RCR','CORONARY','IMPEDANCE','PULMONARY') +# - Data Table Name (string) + +SEGMENT branch0_seg0 0 4.8399151774568026 5 0 1 3.130298369053221 3.1302983615097206 0.0 MAT1 NONE 0.0 0 0 NOBOUND NONE +SEGMENT branch0_seg1 1 1.49226421600263 5 1 2 3.130298345279223 3.130298345279223 0.0 MAT1 NONE 0.0 0 0 NOBOUND NONE +SEGMENT branch0_seg2 2 1.1476824649695037 5 2 3 3.13029837283059 3.1302983690532207 0.0 MAT1 NONE 0.0 0 0 RESISTANCE RESISTANCE_0 + + +DATATABLE RESISTANCE_0 LIST +0.0 100 +0.0 0.0 +ENDDATATABLE + + + +# ================== +# SOLVEROPTIONS CARD +# ================== +# - Solver Time Step (double), +# - Steps Between Saves (int), +# - Max Number of Steps (int) +# - Number of quadrature points for finite elements (int), +# - Name of Datatable for inlet conditions (string) +# - Type of boundary condition (string - 'NOBOUND','PRESSURE','AREA','FLOW','RESISTANCE','RESISTANCE_TIME','PRESSURE_WAVE', +# 'WAVE','RCR','CORONARY','IMPEDANCE','PULMONARY') +# - Convergence tolerance (double), +# - Formulation Type (int - 0 Advective, 1 Conservative), +# - Stabilization (int - 0 No stabilization, 1 With stabilization) + +SOLVEROPTIONS 0.005 20 100 2 NONE COUPLED 1.0e-5 1 1 + +COUPLING ON NEU 1 + +# ============= +# MATERIAL CARD +# ============= +# - Material Name (string) +# - Material Type (string - 'LINEAR','OLUFSEN') +# - Material Density (double) +# - Material Viscosity (double) +# - Material PRef (double) +# - Material Exponent (double) +# - Material Parameter 1 (double) +# - Material Parameter 2 (double) +# - Material Parameter 3 (double) + +MATERIAL MAT1 LINEAR 1.06 0.04 0.0 1.0 10000000.0 0.0 0.0 + +# ============ +# OUTPUT CARD +# ============ +# +# 1. Output file format. The following output types are supported: +# TEXT. The output of every segment is written in separate text files for the flow rate, pressure, area and Reynolds number. The rows contain output values at varying locations along the segment while columns contains results at various time instants. +# VTK. The results for all time steps are plotted to a 3D-like model using the XML VTK file format. +# 2. VTK export option. Two options are available for VTK file outputs: +# 0 - Multiple files (default). A separate file is written for each saved increment. A pvd file is also provided which contains the time information of the sequence. This is the best option to create animations. +# 1 - The results for all time steps are plotted to a single XML VTK file. + +OUTPUT VTK 0 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/outlet_1d_00050.vtp b/tests/cases/fluid/Tpipe_svOneD_2faces/outlet_1d_00050.vtp new file mode 100644 index 000000000..f7573ec53 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/outlet_1d_00050.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0528a3be307a2e54b02f17b763c3ddd98064b2336ea1bd79a80e542e343923b3 +size 57578 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/result_050.vtu b/tests/cases/fluid/Tpipe_svOneD_2faces/result_050.vtu new file mode 100644 index 000000000..a15ed0974 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/result_050.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fb6056a48016488c81f4005c78f08ef506a8af453341e247b1cef9de9f1a838 +size 1768310 diff --git a/tests/cases/fluid/Tpipe_svOneD_2faces/solver.xml b/tests/cases/fluid/Tpipe_svOneD_2faces/solver.xml new file mode 100644 index 000000000..30afe711b --- /dev/null +++ b/tests/cases/fluid/Tpipe_svOneD_2faces/solver.xml @@ -0,0 +1,112 @@ + + + false + 3 + 500 + 0.005 + 0.5 + 10000 + 1 + true + result + 50 + 0.5 + + + mesh-complete/mesh-complete.mesh.vtu + + mesh-complete/mesh-surfaces/cap_main_3d.vtp + + + mesh-complete/mesh-surfaces/cap_main_3d_2.vtp + + + mesh-complete/mesh-surfaces/cap_side_3d.vtp + + + mesh-complete/mesh-surfaces/wall_main_3d.vtp + + + mesh-complete/mesh-surfaces/wall_side_3d.vtp + + + + true + 3 + 20 + 1e-5 + 0.2 + 1.06 + + 0.04 + + + + fsils + + 15 + 1e-4 + 200 + 10 + 1e-3 + 300 + 1e-3 + + + + semi-implicit + /Users/taeoukkim/Desktop/Simvascular_solvers/1Dsolver/svOneDSolver/build/lib/libsvoned_interface.dylib + + + + + true + true + true + true + true + true + + + true + true + true + + + Dir + Coupled + + inlet_1d.in + 100 + 0.0 + 0.3 + + Parabolic + + + Neu + Coupled + + outlet_1d.in + 100 + 0.0 + 0.3 + + + + Neu + Resistance + 100.0 + + + Dirichlet + Steady + 0 + + + Dirichlet + Steady + 0 + + + diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-complete.exterior.vtp b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-complete.exterior.vtp new file mode 100644 index 000000000..324c6ec2b --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-complete.exterior.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6d3ed0de2bbb08a7dc17388e1de4d7504096fbe5abe8d0bf88cab534353efda +size 119781 diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-complete.mesh.vtu b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-complete.mesh.vtu new file mode 100644 index 000000000..58e35a91a --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-complete.mesh.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ba04aa43f6fcc96feb1c349840de4acac0329b6d3f388bf9ec17bc9ff8018ff +size 642824 diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_main_3d.vtp b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_main_3d.vtp new file mode 100644 index 000000000..b98c01a7f --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_main_3d.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:732c605b92af8941d9cce53caf89928fb9de1876498539ad6be1ae6b4c814d69 +size 4910 diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_main_3d_2.vtp b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_main_3d_2.vtp new file mode 100644 index 000000000..4efbbd59b --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_main_3d_2.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5724183e2c702cb93b14c8d35b10cccfbada94c2a54556cf35e3524faac76f35 +size 4828 diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_side_3d.vtp b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_side_3d.vtp new file mode 100644 index 000000000..ee0e3e1cb --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/cap_side_3d.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bfc9a71ddd1b378205bd551f932db3da5f9945590aff9f80d28c294dd18c0f3 +size 3511 diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/wall_main_3d.vtp b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/wall_main_3d.vtp new file mode 100644 index 000000000..5e0c767f5 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/wall_main_3d.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be46a91f418bd88cf8711de48e158b16937c9487278f6d4eafad395d30f68c35 +size 95467 diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/wall_side_3d.vtp b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/wall_side_3d.vtp new file mode 100644 index 000000000..7a2f94350 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/mesh-surfaces/wall_side_3d.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56f42230ac103ace09256b6c49fa5213c1b7945d155565358816f50a17800163 +size 20913 diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/walls_combined.vtp b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/walls_combined.vtp new file mode 100644 index 000000000..08ef317b4 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/mesh-complete/walls_combined.vtp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c049bf671ca4bbc51054e32707305a879cf50fbdf5514d5de5aedadcda34c52 +size 113058 diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/result_050.vtu b/tests/cases/fluid/Tpipe_svZeroD_Dir/result_050.vtu new file mode 100644 index 000000000..156caee26 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/result_050.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:649dc557eb87737a69bbd9cbd1fdbd9ac95f1a3c7b0d37d409938b0ff2489be2 +size 1758470 diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/solver.xml b/tests/cases/fluid/Tpipe_svZeroD_Dir/solver.xml new file mode 100644 index 000000000..c4499cf8f --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/solver.xml @@ -0,0 +1,110 @@ + + + false + 3 + 300 + 0.001 + 0.5 + 10000 + 1 + true + result + 50 + 0.5 + + + mesh-complete/mesh-complete.mesh.vtu + + mesh-complete/mesh-surfaces/cap_main_3d.vtp + + + mesh-complete/mesh-surfaces/cap_main_3d_2.vtp + + + mesh-complete/mesh-surfaces/cap_side_3d.vtp + + + mesh-complete/mesh-surfaces/wall_main_3d.vtp + + + mesh-complete/mesh-surfaces/wall_side_3d.vtp + + + + true + 3 + 20 + 1e-5 + 0.2 + 1.06 + + 0.04 + + + + fsils + + 15 + 1e-4 + 200 + 10 + 1e-3 + 300 + 1e-3 + + + + semi-implicit + solver_0d.json + /Users/taeoukkim/Desktop/Simvascular_solvers/0Dsolver/svZeroDSolver/build/src/interface/libsvzero_interface.dylib + 0.0 + 0.0 + + + + + true + true + true + true + true + true + + + true + true + true + + + Dir + Coupled + + cp1 + 100 + 0.0 + 0.2 + + Parabolic + + + Neu + Resistance + 100.0 + + + Neu + Resistance + 100.0 + + + Dirichlet + Steady + 0 + + + Dirichlet + Steady + 0 + + + diff --git a/tests/cases/fluid/Tpipe_svZeroD_Dir/solver_0d.json b/tests/cases/fluid/Tpipe_svZeroD_Dir/solver_0d.json new file mode 100644 index 000000000..5a3f74346 --- /dev/null +++ b/tests/cases/fluid/Tpipe_svZeroD_Dir/solver_0d.json @@ -0,0 +1,52 @@ +{ + "boundary_conditions": [ + { + "bc_name": "INFLOW", + "bc_type": "FLOW", + "bc_values": { + "Q": [ + 100.0, + 100.0 + ], + "t": [ + 0.0, + 1.0 + ] + } + } + ], + "external_solver_coupling_blocks": [ + { + "name": "cp1", + "type": "PRESSURE", + "location": "outlet", + "connected_block": "vessel1", + "periodic": false, + "values": { + "t": [0.0, 1.0], + "P": [1.0, 1.0] + } + } + ], + "junctions": [], + "simulation_parameters": { + "coupled_simulation": true, + "number_of_time_pts": 10, + "output_all_cycles": true, + "steady_initial": false + }, + "vessels": [ + { + "boundary_conditions": { + "inlet": "INFLOW" + }, + "vessel_id": 0, + "vessel_length": 1.0, + "vessel_name": "vessel1", + "zero_d_element_type": "BloodVessel", + "zero_d_element_values": { + "R_poiseuille": 100.0 + } + } + ] +} \ No newline at end of file diff --git a/tests/cases/fluid/pipe_svOneD_2faces/solver.xml b/tests/cases/fluid/pipe_svOneD_2faces/solver.xml deleted file mode 100644 index bc15d6bad..000000000 --- a/tests/cases/fluid/pipe_svOneD_2faces/solver.xml +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - false - 3 - 100 - 0.001 - 0.50 - STOP_SIM - - 1 - result - 10 - 1 - - 100 - 0 - - 1 - 0 - 0 - - - - - - mesh-complete/mesh-complete.mesh.vtu - - - mesh-complete/mesh-surfaces/lumen_inlet.vtp - - - - mesh-complete/mesh-surfaces/lumen_outlet1.vtp - - - - mesh-complete/mesh-surfaces/lumen_outlet2.vtp - - - - mesh-complete/mesh-surfaces/lumen_wall.vtp - - - - - - 1 - 3 - 10 - 1e-3 - 0.2 - - 1.06 - - 0.04 - - - - true - true - true - true - - - - - fsils - - 10 - 3 - 500 - 1e-3 - 1e-3 - 1e-3 - 50 - - - - - semi-implicit - /path/to/svOneDSolver/build/lib/libsvOneDSolver_interface.so - - - - - Dir - Unsteady - lumen_inlet.flw - true - true - - - - - Neu - Coupled - - 1dmodel1.in - - - - - - Neu - Coupled - - 1dmodel2.in - - - - - - Dir - Steady - 0.0 - - - - - From b97873d78ea1b5ba42b0ad8a28697682c2deede7 Mon Sep 17 00:00:00 2001 From: Taeouk Kim Date: Mon, 22 Jun 2026 17:33:14 -0700 Subject: [PATCH 51/51] second review change --- Code/Source/solver/ComMod.h | 27 +++++++++------ Code/Source/solver/svOneD_interface.cpp | 21 ++++++------ .../svOneD_interface/OneDSolverInterface.cpp | 34 ++++++++++++++----- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/Code/Source/solver/ComMod.h b/Code/Source/solver/ComMod.h index 444f5e467..1a3a43893 100644 --- a/Code/Source/solver/ComMod.h +++ b/Code/Source/solver/ComMod.h @@ -817,24 +817,29 @@ class svZeroDSolverInterfaceData void set_data(const svZeroDSolverInterfaceParameters& params); }; -//---------------------------- -// svOneDSolverInterfaceData -//---------------------------- -// This class stores information used to interface to the svOneDSolver. -// +/// \brief Stores information used to interface with svOneDSolver. +/// +/// This class stores the global svOneDSolver interface settings read from the +/// solver XML file. Per-face 1D input files are stored separately in +/// cplFaceType::oned_input_file. class svOneDSolverInterfaceData { public: - // Path to the 1D solver shared library (without .so/.dylib extension, - // or with extension if the full path is provided). + /// \brief Path to the svOneDSolver interface shared library. + /// + /// This may be provided either with the platform-specific extension + /// (.so/.dylib) or without it. std::string solver_library; - // If the data has been set for the interface. Set to true after - // the svOneDSolver_interface XML element has been parsed. - // Note: the per-face input files are stored in cplFaceType::oned_input_file. + /// \brief True if the svOneDSolver interface settings were read from XML. + /// + /// This is set to true after the svOneDSolver_interface XML element has + /// been parsed successfully. bool has_data = false; - // Read svOneDSolver interface information from parameters. + /// \brief Read svOneDSolver interface settings from parsed parameters. + /// + /// \param params Parsed svOneDSolver interface parameters. void set_data(const svOneDSolverInterfaceParameters& params); }; diff --git a/Code/Source/solver/svOneD_interface.cpp b/Code/Source/solver/svOneD_interface.cpp index 294ec0b73..c0004759c 100644 --- a/Code/Source/solver/svOneD_interface.cpp +++ b/Code/Source/solver/svOneD_interface.cpp @@ -176,7 +176,7 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) if (!solver_if.has_data) { throw svmp::CoreException( - "[svOneD::init_svOneD] svOneD solver interface data is missing.", + "[svOneD::init_svOneD] svOneD solver interface data is missing. Please check the XML input file.", svmp::StatusCode::InvalidState, __FILE__, __LINE__, @@ -210,7 +210,7 @@ void init_svOneD(ComMod& com_mod, const CmMod& cm_mod) if (oned_models.empty()) { throw svmp::CoreException( - "[svOneD::init_svOneD] No svOneD-coupled faces with input files found.", + "[svOneD::init_svOneD] No svOneD-coupled faces with input files found. Please check the XML input file.", svmp::StatusCode::InvalidState, __FILE__, __LINE__, @@ -366,16 +366,17 @@ void calc_svOneD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) int save_incr = com_mod.saveIncr; int error_code = 0; - st.interface->run_simulation(st.problem_id, t_old, save_incr, - st.coupling_type, params, - work_sol.data(), cpl_values[k], BCFlag, error_code); - - if (error_code != 0) { + try { + st.interface->run_simulation(st.problem_id, t_old, save_incr, + st.coupling_type, params, + work_sol.data(), cpl_values[k], BCFlag, error_code); + } catch (const std::exception& e) { svmp::raise( SVMP_HERE, - "[svOneD::calc_svOneD] 1D solver step for face '" + - bc.coupled_bc.get_oned_input_file() + "' failed with error code " + - std::to_string(error_code)); + "[svOneD::calc_svOneD] 1D solver step failed for svOneD input file '" + + bc.coupled_bc.get_oned_input_file() + "' with coupling type '" + + st.coupling_type + "' at time " + std::to_string(t_new) + ". " + + "Original error: " + e.what()); } // Commit the updated solution only on the final iteration. diff --git a/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp b/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp index b0fb451be..24f193263 100644 --- a/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp +++ b/Code/Source/solver/svOneD_interface/OneDSolverInterface.cpp @@ -38,8 +38,8 @@ void OneDSolverInterface::load_library(const std::string& interface_lib) const char* err = dlerror(); if (err) { throw svmp::CoreException( - std::string("[OneDSolverInterface] Could not load symbol '") + - name + "': " + err, + std::string("[OneDSolverInterface] Could not find the 1D interface function '") + + name + "' in the shared library '" + interface_lib + "': " + err, svmp::StatusCode::DependencyError, __FILE__, __LINE__, @@ -63,7 +63,10 @@ void OneDSolverInterface::initialize(const std::string& input_file, { if (!initialize_1d_) { throw svmp::CoreException( - "[OneDSolverInterface] initialize_1d not loaded", + "[OneDSolverInterface] Cannot initialize the 1D solver because the " + "1D interface library has not been loaded. Call load_library() with " + "the svOneDSolver interface shared library before initialize(), and " + "check that the library path is correct.", svmp::StatusCode::DependencyError, __FILE__, __LINE__, @@ -123,20 +126,35 @@ void OneDSolverInterface::run_simulation(int problem_id, double current_time, { if (!run_1d_simulation_step_1d_) { throw svmp::CoreException( - "[OneDSolverInterface] run_1d_simulation_step_1d not loaded", + "[OneDSolverInterface] Cannot run the 1D solver because the " + "run_1d_simulation_step_1d interface function is not available. " + "Check that the svOneDSolver interface shared library was loaded correctly.", svmp::StatusCode::DependencyError, __FILE__, __LINE__, __func__); } - // Copy coupling_type into a mutable buffer (shared-library uses char*). + std::vector ctype_buf(coupling_type.begin(), coupling_type.end()); ctype_buf.push_back('\0'); - // Copy last_flag into a mutable single-character buffer. + char flag_buf[2] = { last_flag, '\0' }; + + error_code = 0; + run_1d_simulation_step_1d_(problem_id, current_time, save_incr, - ctype_buf.data(), params, solution, - cpl_value, flag_buf, error_code); + ctype_buf.data(), params, solution, + cpl_value, flag_buf, error_code); + + if (error_code != 0) { + throw svmp::CoreException( + "[OneDSolverInterface] svOneDSolver failed while advancing the 1D model. " + "The 1D solver returned error code " + std::to_string(error_code) + ".", + svmp::StatusCode::DependencyError, + __FILE__, + __LINE__, + __func__); + } } void OneDSolverInterface::extract_coupled_dof(int problem_id, int& coupled_dof,