Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Code/Source/solver/ComMod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ void svZeroDSolverInterfaceType::set_data(const svZeroDSolverInterfaceParameters
initial_pressures = params.initial_pressures();
}

// Copy unit conversion factors if provided
if (params.pressure_conversion_factor.defined()) {
pressure_conversion = params.pressure_conversion_factor();
}
if (params.flowrate_conversion_factor.defined()) {
flowrate_conversion = params.flowrate_conversion_factor();
}

has_data = true;
}

Expand Down
6 changes: 6 additions & 0 deletions Code/Source/solver/ComMod.h
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,12 @@ class svZeroDSolverInterfaceType
// the svZeroDSolver_interface XML parameter has been defined.
bool has_data = false;

// Unit conversion factors between 3D (svMultiPhysics) and 0D (svZeroD)
// When sending values to 0D, multiply by these; when receiving from 0D,
// divide by these. Defaults are 1.0 (no conversion).
double pressure_conversion = 1.0;
double flowrate_conversion = 1.0;

void set_data(const svZeroDSolverInterfaceParameters& params);
void add_block_face(const std::string& block_name, const std::string& face_name);
};
Expand Down
4 changes: 4 additions & 0 deletions Code/Source/solver/Parameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,10 @@ svZeroDSolverInterfaceParameters::svZeroDSolverInterfaceParameters()

set_parameter("Shared_library", "", required, shared_library);

// Unit conversion factors (default 1.0 means no scaling).
set_parameter("Pressure_conversion_factor", 1.0, !required, pressure_conversion_factor);
set_parameter("Flowrate_conversion_factor", 1.0, !required, flowrate_conversion_factor);

};

void svZeroDSolverInterfaceParameters::set_values(tinyxml2::XMLElement* xml_elem)
Expand Down
7 changes: 7 additions & 0 deletions Code/Source/solver/Parameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,13 @@ class svZeroDSolverInterfaceParameters : public ParameterLists

Parameter<std::string> shared_library;

// Unit conversion factors between 3D and 0D models
// Pressure and flowrate values will be multiplied by these factors before
// being sent to the 0D model, and the inverse factors will be applied to
// 0D outputs before returning to 3D.
Parameter<double> pressure_conversion_factor;
Parameter<double> flowrate_conversion_factor;

bool value_set = false;
};

Expand Down
22 changes: 13 additions & 9 deletions Code/Source/solver/svZeroD_subroutines.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,11 +352,13 @@ void init_svZeroD(ComMod& com_mod, const CmMod& cm_mod)
interface->return_ydot(last_state_ydot);
for (int s = 0; s < numCoupledSrfs; ++s) {
if (init_flow_flag == 1) {
lpn_state_y[sol_IDs[2 * s]] = init_flow;
// Apply flowrate conversion when initializing state
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment indicates conversion is applied when initializing state, but doesn't explain the direction. The README states the conversion factor transforms 3D units to 0D units (e.g., dyn/cm² → mmHg), but init_flow is a 3D value being stored in lpn_state_y (0D state). Consider clarifying whether init_flow should be in 3D or 0D units, and if the conversion direction is correct here.

Suggested change
// Apply flowrate conversion when initializing state
// Convert init_flow from 3D units to 0D units when initializing state

Copilot uses AI. Check for mistakes.
lpn_state_y[sol_IDs[2 * s]] = init_flow * solver_interface.flowrate_conversion;
cplBC.fa[s].y = lpn_state_y[sol_IDs[2 * s]];
}
if (init_press_flag == 1) {
lpn_state_y[sol_IDs[2 * s + 1]] = init_press;
// Apply pressure conversion when initializing state
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the flowrate initialization, the comment doesn't clarify the direction of conversion. Consider adding details about whether init_press is expected to be in 3D or 0D units, and confirming the multiplication is the correct operation for this initialization path.

Suggested change
// Apply pressure conversion when initializing state
// Apply pressure conversion when initializing state.
// init_press is expected to be in 3D units (e.g., dyn/cm^2 or mmHg).
// The solver expects pressure in 0D units.
// Multiplying by solver_interface.pressure_conversion converts from 3D to 0D units.

Copilot uses AI. Check for mistakes.
lpn_state_y[sol_IDs[2 * s + 1]] = init_press * solver_interface.pressure_conversion;
cplBC.fa[s].y = lpn_state_y[sol_IDs[2 * s + 1]];
}
}
Expand Down Expand Up @@ -434,14 +436,14 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) {

total_flow = 0.0;

// Update pressure and flow in the zeroD model
// Update pressure and flow in the zeroD model (apply unit conversions)
for (int i = 0; i < numCoupledSrfs; ++i) {
if (i < nDir) {
params[0] = PCoupled[i];
params[1] = PnCoupled[i];
params[0] = PCoupled[i] * cplBC.svzerod_solver_interface.pressure_conversion;
params[1] = PnCoupled[i] * cplBC.svzerod_solver_interface.pressure_conversion;
} else {
params[0] = in_out_sign[i] * QCoupled[i];
params[1] = in_out_sign[i] * QnCoupled[i];
params[0] = in_out_sign[i] * QCoupled[i] * cplBC.svzerod_solver_interface.flowrate_conversion;
params[1] = in_out_sign[i] * QnCoupled[i] * cplBC.svzerod_solver_interface.flowrate_conversion;
total_flow += QCoupled[i];
}
update_svZeroD_block_params(svzd_blk_names[i], times, params);
Expand All @@ -455,10 +457,12 @@ void calc_svZeroD(ComMod& com_mod, const CmMod& cm_mod, char BCFlag) {

for (int i = 0; i < numCoupledSrfs; ++i) {
if (i < nDir) {
QCoupled[i] = in_out_sign[i] * lpn_state_y[sol_IDs[2 * i]];
// Convert 0D flow back to 3D units
QCoupled[i] = (in_out_sign[i] * lpn_state_y[sol_IDs[2 * i]]) / cplBC.svzerod_solver_interface.flowrate_conversion;
cplBC.fa[i].y = QCoupled[i];
} else {
PCoupled[i] = lpn_state_y[sol_IDs[2 * i + 1]];
// Convert 0D pressure back to 3D units
PCoupled[i] = lpn_state_y[sol_IDs[2 * i + 1]] / cplBC.svzerod_solver_interface.pressure_conversion;
cplBC.fa[i].y = PCoupled[i];
}
}
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fluid/pipe_RCR_sv0D/svzerod_3Dcoupling.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"bc_name": "RCR",
"bc_type": "RCR",
"bc_values": {
"_comment": "cgs units. R: (dyn/cm^2).s/cm^3, C: cm^3/(dyn/cm^2), Pd: (dyn/cm^2)",
"Rp": 121.0,
"Rd": 1212.0,
"C": 1.5e-4,
Expand Down
14 changes: 14 additions & 0 deletions tests/cases/fluid/pipe_RCR_sv0D_different_units/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# pipe_RCR_sv0D_units

This test is identical to `fluid/pipe_RCR_sv0D` but uses mixed units:

- solver.xml: cgs units (dynes/cm^2, cm^3/s)
- svZeroD JSON: clinical units (mmHg, mL/s)

The `svZeroDSolver_interface` specifies conversion factors so that values are converted automatically:

- `Pressure_conversion_factor = 0.00075006157584566` (dynes/cm^2 -> mmHg)
- `Flowrate_conversion_factor = 1.0` (cm^3/s -> mL/s)

Mesh and inflow files are referenced from the original test directory `pipe_RCR_sv0D/` to avoid duplication.
Also, the reference result `result_002.vtu` in `pipe_RCR_sv0D/` is used for comparison.
111 changes: 111 additions & 0 deletions tests/cases/fluid/pipe_RCR_sv0D_different_units/solver.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" ?>
<svMultiPhysicsFile version="0.1">

<GeneralSimulationParameters>

<Continue_previous_simulation> false </Continue_previous_simulation>
<Number_of_spatial_dimensions> 3 </Number_of_spatial_dimensions>
<Number_of_time_steps> 2 </Number_of_time_steps>
<Time_step_size> 0.005 </Time_step_size>
<Spectral_radius_of_infinite_time_step> 0.50 </Spectral_radius_of_infinite_time_step>
<Searched_file_name_to_trigger_stop> STOP_SIM </Searched_file_name_to_trigger_stop>

<Save_results_to_VTK_format> 1 </Save_results_to_VTK_format>
<Name_prefix_of_saved_VTK_files> result </Name_prefix_of_saved_VTK_files>
<Increment_in_saving_VTK_files> 1 </Increment_in_saving_VTK_files>
<Start_saving_after_time_step> 1 </Start_saving_after_time_step>

<Increment_in_saving_restart_files> 200 </Increment_in_saving_restart_files>
<Convert_BIN_to_VTK_format> 0 </Convert_BIN_to_VTK_format>

<Verbose> 1 </Verbose>
<Warning> 0 </Warning>
<Debug> 0 </Debug>

</GeneralSimulationParameters>

<Add_mesh name="msh" >

<Mesh_file_path> ../pipe_RCR_sv0D/mesh-complete/mesh-complete.mesh.vtu </Mesh_file_path>

<Add_face name="lumen_inlet">
<Face_file_path> ../pipe_RCR_sv0D/mesh-complete/mesh-surfaces/lumen_inlet.vtp </Face_file_path>
</Add_face>

<Add_face name="lumen_outlet">
<Face_file_path> ../pipe_RCR_sv0D/mesh-complete/mesh-surfaces/lumen_outlet.vtp </Face_file_path>
</Add_face>

<Add_face name="lumen_wall">
<Face_file_path> ../pipe_RCR_sv0D/mesh-complete/mesh-surfaces/lumen_wall.vtp </Face_file_path>
</Add_face>

</Add_mesh>

<Add_equation type="fluid" >
<Coupled> 1 </Coupled>
<Min_iterations> 3 </Min_iterations>
<Max_iterations> 10 </Max_iterations>
<Tolerance> 1e-3 </Tolerance>
<Backflow_stabilization_coefficient> 0.2 </Backflow_stabilization_coefficient>

<Density> 1.06 </Density> <!-- g/cm^3 -->
<Viscosity model="Constant" >
<Value> 0.04 </Value> <!-- poise -->
</Viscosity>

<Output type="Spatial" >
<Velocity> true </Velocity>
<Pressure> true </Pressure>
<Traction> true </Traction>
<WSS> true </WSS>
<Vorticity> true </Vorticity>
<Divergence> true </Divergence>
</Output>

<LS type="NS" >
<Linear_algebra type="fsils" >
<Preconditioner> fsils </Preconditioner>
</Linear_algebra>
<Max_iterations> 10 </Max_iterations>
<NS_GM_max_iterations> 3 </NS_GM_max_iterations>
<NS_CG_max_iterations> 500 </NS_CG_max_iterations>
<Tolerance> 1e-3 </Tolerance>
<NS_GM_tolerance> 1e-3 </NS_GM_tolerance>
<NS_CG_tolerance> 1e-3 </NS_CG_tolerance>
<Krylov_space_dimension> 50 </Krylov_space_dimension>
</LS>

<svZeroDSolver_interface>
<Coupling_type> semi-implicit </Coupling_type>
<Configuration_file> svzerod_3Dcoupling.json </Configuration_file>
<Shared_library> ../../../../svZeroDSolver/build/src/interface/libsvzero_interface.dylib </Shared_library>
<Initial_flows> 0.0 </Initial_flows>
<Initial_pressures> 0.0 </Initial_pressures>
<Pressure_conversion_factor> 0.00075006157584566 </Pressure_conversion_factor> <!-- dyn/cm^2 to mmHg -->
<Flowrate_conversion_factor> 1.0 </Flowrate_conversion_factor> <!-- cm^3/s to ml/s -->
</svZeroDSolver_interface>

<Add_BC name="lumen_inlet" >
<Type> Dir </Type>
<Time_dependence> Unsteady </Time_dependence>
<Temporal_values_file_path> ../pipe_RCR_sv0D/lumen_inlet.flw</Temporal_values_file_path>
<Zero_out_perimeter> true </Zero_out_perimeter>
<Impose_flux> true </Impose_flux>
</Add_BC>

<Add_BC name="lumen_outlet" >
<Type> Neu </Type>
<Time_dependence> Coupled </Time_dependence>
<svZeroDSolver_block> RCR_coupling </svZeroDSolver_block>
</Add_BC>

<Add_BC name="lumen_wall" >
<Type> Dir </Type>
<Time_dependence> Steady </Time_dependence>
<Value> 0.0 </Value>
</Add_BC>

</Add_equation>

</svMultiPhysicsFile>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"simulation_parameters": {
"coupled_simulation": true,
"number_of_time_pts": 100,
"output_all_cycles": true,
"steady_initial": false
},
"boundary_conditions": [
{
"bc_name": "RCR",
"bc_type": "RCR",
"bc_values": {
"_comment": "Clinical units. R: mmHg.s/mL, C: mL/mmHg, Pd: mmHg",
"Rp": 0.09075745067,
"Rd": 0.90907462992,
"C": 0.19998358112,
"Pd": 0.0
}
}
],
"external_solver_coupling_blocks": [
{
"name": "RCR_coupling",
"type": "FLOW",
"location": "inlet",
"connected_block": "RCR",
"periodic": false,
"values": {
"t": [0.0, 1.0],
"Q": [1.0, 1.0]
}
}
],
"junctions": [],
"vessels": []
}
4 changes: 4 additions & 0 deletions tests/test_fluid.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ def test_pipe_RCR_sv0D(n_proc):
t_max = 2
run_with_reference(base_folder, test_folder, fields, n_proc, t_max)

def test_pipe_RCR_sv0D_different_units(n_proc):
test_folder = "pipe_RCR_sv0D_different_units"
t_max = 2
run_with_reference(base_folder, test_folder, fields, n_proc, t_max, name_ref="../pipe_RCR_sv0D/result_002.vtu")

def test_driven_cavity_2d(n_proc):
test_folder = "driven_cavity_2d"
Expand Down
Loading