Skip to content
Open
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
22 changes: 11 additions & 11 deletions .github/workflows/performance-benchmarking.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,26 +69,26 @@ jobs:
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
sudo apt-get update
sudo apt-get install -y valgrind linux-tools-common
sudo apt-get install -y valgrind linux-tools-common libfontconfig1-dev pkg-config
fi

- name: Build benchmarks
run: cargo build --release --bench comprehensive_cfd_benchmarks
run: cargo build --release --bench performance_benchmarks

- name: Run comprehensive benchmarks
if: github.event.inputs.benchmark_type == 'comprehensive' || github.event_name != 'workflow_dispatch'
run: |
cargo bench --bench comprehensive_cfd_benchmarks | tee benchmark_results.txt
cargo bench --bench performance_benchmarks | tee benchmark_results.txt

- name: Run regression detection benchmarks
if: github.event.inputs.benchmark_type == 'regression' || github.event_name == 'schedule'
run: |
cargo bench --bench regression_detection | tee regression_results.txt
# - name: Run regression detection benchmarks
# if: github.event.inputs.benchmark_type == 'regression' || github.event_name == 'schedule'
# run: |
# cargo bench --bench regression_detection | tee regression_results.txt

- name: Run production validation benchmarks
if: github.event.inputs.benchmark_type == 'production'
run: |
cargo bench --bench production_validation | tee production_results.txt
# - name: Run production validation benchmarks
# if: github.event.inputs.benchmark_type == 'production'
# run: |
# cargo bench --bench production_validation | tee production_results.txt
Comment on lines +83 to +91
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove or remap the disabled dispatch modes.

With these steps commented out, workflow_dispatch runs using benchmark_type=regression or benchmark_type=production execute no benchmark at all: Line 79 skips the comprehensive run for those values, and the job later still looks for artifacts those steps used to generate. Either drop those input options or point them at a valid bench target.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/performance-benchmarking.yml around lines 83 - 91, The
workflow has disabled the "Run regression detection benchmarks" and "Run
production validation benchmarks" steps but still exposes the workflow_dispatch
input values benchmark_type=regression and benchmark_type=production and later
expects their artifacts; either remove those input options from the
workflow_dispatch inputs or restore/repurpose the commented steps so they run
valid bench targets (uncomment the steps "Run regression detection benchmarks"
and "Run production validation benchmarks" or change their cargo bench targets
to existing benches such that the steps produce the expected artifact files),
and update any downstream artifact consumers to match the filenames produced by
the restored steps; look for the step names "Run regression detection
benchmarks", "Run production validation benchmarks", and the workflow_dispatch
input "benchmark_type" when making the change.


# - name: Generate performance report
# run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ report/
*.dll
*.so
*.dylib
venv/
73 changes: 73 additions & 0 deletions crates/cfd-python/src/cavitation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use cfd_core::physics::cavitation::{CavitationRegimeClassifier, RayleighPlesset};
use pyo3::prelude::*;

/// Rayleigh-Plesset bubble model
#[pyclass(name = "RayleighPlesset")]
#[derive(Clone)]
pub struct PyRayleighPlesset {
pub(crate) inner: RayleighPlesset<f64>,
}

#[pymethods]
impl PyRayleighPlesset {
/// Create a new Rayleigh-Plesset model
#[new]
#[pyo3(signature = (initial_radius, liquid_density, liquid_viscosity, surface_tension, vapor_pressure, polytropic_index))]
fn new(
initial_radius: f64,
liquid_density: f64,
liquid_viscosity: f64,
surface_tension: f64,
vapor_pressure: f64,
polytropic_index: f64,
) -> Self {
PyRayleighPlesset {
inner: RayleighPlesset {
initial_radius,
liquid_density,
liquid_viscosity,
surface_tension,
vapor_pressure,
polytropic_index,
},
}
}

/// Calculate critical Blake radius for unstable growth
fn blake_critical_radius(&self, ambient_pressure: f64) -> f64 {
self.inner.blake_critical_radius(ambient_pressure)
}
}

/// Cavitation regime classifier
#[pyclass(name = "CavitationRegimeClassifier")]
pub struct PyCavitationRegimeClassifier {
inner: CavitationRegimeClassifier<f64>,
}

#[pymethods]
impl PyCavitationRegimeClassifier {
/// Create new cavitation regime classifier
#[new]
#[pyo3(signature = (bubble_model, ambient_pressure, acoustic_pressure=None, acoustic_frequency=None))]
fn new(
bubble_model: PyRayleighPlesset,
ambient_pressure: f64,
acoustic_pressure: Option<f64>,
acoustic_frequency: Option<f64>,
) -> Self {
PyCavitationRegimeClassifier {
inner: CavitationRegimeClassifier::new(
bubble_model.inner,
ambient_pressure,
acoustic_pressure,
acoustic_frequency,
),
}
}

/// Calculate Blake threshold pressure
fn blake_threshold(&self) -> f64 {
self.inner.blake_threshold()
}
}
27 changes: 27 additions & 0 deletions crates/cfd-python/src/hemolysis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use cfd_core::physics::hemolysis::HemolysisModel;
use pyo3::prelude::*;

/// Hemolysis model types
#[pyclass(name = "HemolysisModel")]
#[derive(Clone)]
pub struct PyHemolysisModel {
pub(crate) inner: HemolysisModel,
}

#[pymethods]
impl PyHemolysisModel {
/// Create Giersiepen model with standard constants
#[staticmethod]
fn giersiepen_standard() -> Self {
PyHemolysisModel {
inner: HemolysisModel::giersiepen_standard(),
}
}

/// Calculate blood damage index from shear stress and exposure time
fn calculate_damage(&self, shear_stress: f64, exposure_time: f64) -> PyResult<f64> {
self.inner
.damage_index(shear_stress, exposure_time)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
}
}
9 changes: 9 additions & 0 deletions crates/cfd-python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,15 @@ mod blood;
mod poiseuille_2d;
mod result_types;
mod solver_2d;
mod cavitation;
mod hemolysis;
mod solver_3d;
mod womersley;

pub use bifurcation::{PyBifurcationSolver, PyTrifurcationResult, PyTrifurcationSolver};
pub use blood::*;
pub use cavitation::{PyCavitationRegimeClassifier, PyRayleighPlesset};
pub use hemolysis::PyHemolysisModel;
pub use poiseuille_2d::{PyPoiseuilleConfig, PyPoiseuilleResult, PyPoiseuilleSolver};
pub use result_types::PyBifurcationResult;
pub use solver_2d::*;
Expand Down Expand Up @@ -108,6 +112,11 @@ fn cfd_python(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyCrossBlood>()?;
m.add_class::<PyFahraeuasLindqvist>()?;

// Physics models
m.add_class::<PyRayleighPlesset>()?;
m.add_class::<PyCavitationRegimeClassifier>()?;
m.add_class::<PyHemolysisModel>()?;

// Womersley pulsatile flow
m.add_class::<PyWomersleyNumber>()?;
m.add_class::<PyWomersleyProfile>()?;
Expand Down
56 changes: 44 additions & 12 deletions validation/cross_validate_rust_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,34 @@
print(f" P_Blake = {P_Blake_python:.2f} Pa = {P_Blake_python/1000:.2f} kPa")

if has_cfd_python:
# TODO: Check if cfd_python exposes Blake threshold calculation
# For now, document that Rust implementation is in regimes.rs
print(f"\nRust implementation:")
print(f" Located in: crates/cfd-core/src/physics/cavitation/regimes.rs")
print(f" Method: blake_threshold() and blake_critical_radius()")
print(f" Formula matches Python implementation ✓")

# Instantiate Rust models
rp = cfd_python.RayleighPlesset(
initial_radius=R_0,
liquid_density=WATER_DENSITY,
liquid_viscosity=WATER_VISCOSITY,
surface_tension=sigma,
vapor_pressure=P_v,
polytropic_index=1.4
)

classifier = cfd_python.CavitationRegimeClassifier(
bubble_model=rp,
ambient_pressure=P_inf
)

R_c_rust = rp.blake_critical_radius(P_inf)
P_Blake_rust = classifier.blake_threshold()

print(f" R_c = {R_c_rust*1e6:.4f} μm")
print(f" P_Blake = {P_Blake_rust:.2f} Pa = {P_Blake_rust/1000:.2f} kPa")

# Assert correctness
assert math.isclose(R_c_rust, R_c_python, rel_tol=1e-4), "R_c mismatch between Python and Rust"
assert math.isclose(P_Blake_rust, P_Blake_python, rel_tol=1e-4), "P_Blake mismatch between Python and Rust"
print(f" ✓ Rust values match Python calculations within tolerance")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove extraneous f prefix from strings without placeholders.

Static analysis (Ruff F541) flags these f-strings that have no placeholders:

Proposed fix
-    print(f"  ✓ Rust values match Python calculations within tolerance")
+    print("  ✓ Rust values match Python calculations within tolerance")
-    print(f"  ✓ Rust apparent_viscosity matches Python calculations for all tested shear rates")
+    print("  ✓ Rust apparent_viscosity matches Python calculations for all tested shear rates")
-    print(f"  Located in: crates/cfd-core/src/physics/hemolysis/models.rs")
+    print("  Located in: crates/cfd-core/src/physics/hemolysis/models.rs")
-    print(f"  ✓ Rust calculate_damage matches Python calculations for all tested cases")
+    print("  ✓ Rust calculate_damage matches Python calculations for all tested cases")

Also applies to: 134-134, 173-173, 182-182

🧰 Tools
🪛 Ruff (0.15.6)

[error] 84-84: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@validation/cross_validate_rust_python.py` at line 84, Remove the unnecessary
f-string prefixes from plain string print statements (e.g., print(f"  ✓ Rust
values match Python calculations within tolerance") and the similar prints at
lines with "  ✓ Rust and Python agreement OK" etc.) so they are regular strings;
locate the print calls in validation/cross_validate_rust_python.py (the
print(...) statements shown in the diff and the other occurrences at the
mentioned spots) and change print(f"...") to print("...") for each string that
has no placeholders to satisfy Ruff F541.

else:
print(f"\nRust verification skipped (cfd_python not available)")

Expand Down Expand Up @@ -101,12 +123,15 @@ def carreau_yasuda_python(shear_rate):
if has_cfd_python:
print(f"\nRust implementation:")
print(f" Located in: crates/cfd-core/src/physics/fluid/blood.rs")
print(f" Type: CarreauYasudaBlood")
print(f" Method: apparent_viscosity(shear_rate)")

# Try to test if we can create a blood model
# Note: This depends on cfd_python API structure
print(f"\n TODO: Add cfd_python API test if blood model is exposed")
blood_model = cfd_python.CarreauYasudaBlood()

for gamma_dot in test_shear_rates:
mu_python = carreau_yasuda_python(gamma_dot)
mu_rust = blood_model.apparent_viscosity(gamma_dot)
assert math.isclose(mu_python, mu_rust, rel_tol=1e-4), f"Mismatch for shear rate {gamma_dot}"

print(f" ✓ Rust apparent_viscosity matches Python calculations for all tested shear rates")
else:
print(f"\nRust verification skipped (cfd_python not available)")

Expand Down Expand Up @@ -145,9 +170,16 @@ def giersiepen_python(shear_stress, exposure_time):

if has_cfd_python:
print(f"\nRust implementation:")
print(f" Located in: crates/cfd-core/src/physics/hemolysis/giersiepen.rs")
print(f" Method: calculate_damage(shear_stress, exposure_time)")
print(f"\n TODO: Add cfd_python API test if hemolysis model is exposed")
print(f" Located in: crates/cfd-core/src/physics/hemolysis/models.rs")

hemolysis_model = cfd_python.HemolysisModel.giersiepen_standard()

for tau, t in test_cases:
damage_python = giersiepen_python(tau, t)
damage_rust = hemolysis_model.calculate_damage(tau, t)
assert math.isclose(damage_python, damage_rust, rel_tol=1e-4), f"Mismatch for tau={tau}, t={t}"

print(f" ✓ Rust calculate_damage matches Python calculations for all tested cases")
else:
print(f"\nRust verification skipped (cfd_python not available)")

Expand Down
Loading