From e374c445604d3e88559e864ba6615627605bc46a Mon Sep 17 00:00:00 2001 From: simonsantama Date: Thu, 8 Jan 2026 20:21:42 +0000 Subject: [PATCH 01/22] Initial implemetnatino of rust for all equations by CoPilot --- crates/sfpe_handbook/src/chapter_50.rs | 3 ++ .../src/chapter_50/equation_50_18.rs | 31 +++++++++++++++++++ .../src/chapter_50/equation_50_19.rs | 31 +++++++++++++++++++ .../src/chapter_50/equation_50_20.rs | 31 +++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 crates/sfpe_handbook/src/chapter_50/equation_50_18.rs create mode 100644 crates/sfpe_handbook/src/chapter_50/equation_50_19.rs create mode 100644 crates/sfpe_handbook/src/chapter_50/equation_50_20.rs diff --git a/crates/sfpe_handbook/src/chapter_50.rs b/crates/sfpe_handbook/src/chapter_50.rs index 069e53a..f7dde78 100644 --- a/crates/sfpe_handbook/src/chapter_50.rs +++ b/crates/sfpe_handbook/src/chapter_50.rs @@ -2,7 +2,10 @@ pub mod equation_50_1; pub mod equation_50_14; pub mod equation_50_15; pub mod equation_50_16; +pub mod equation_50_18; +pub mod equation_50_19; pub mod equation_50_2; +pub mod equation_50_20; pub mod equation_50_4; pub mod equation_50_6; pub mod equation_50_7; diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs new file mode 100644 index 0000000..b0a0a9f --- /dev/null +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs @@ -0,0 +1,31 @@ +pub fn factor(a_sb: f64, a_bo: f64, t_b: f64, t_s: f64) -> f64 { + let numerator = a_sb.powf(2.0) * (t_b + 273.0); + let denominator = a_bo.powf(2.0) * (t_s + 273.0); + 1.0 + numerator / denominator +} + +#[cfg(not(coverage))] +pub fn factor_equation( + f_r: String, + a_sb: String, + t_b: String, + a_bo: String, + t_s: String, +) -> String { + format!( + "{} = 1 + \\frac{{ {}^2 \\times ( {} + 273 ) }}{{ {}^2 \\times ( {} + 273 ) }}", + f_r, a_sb, t_b, a_bo, t_s + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_factor() { + let result = factor(0.005, 0.005, 15.0, 5.0); + let expected = 2.035971223; + assert!((result - expected).abs() < 1e-6); + } +} \ No newline at end of file diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs new file mode 100644 index 0000000..b0a0a9f --- /dev/null +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs @@ -0,0 +1,31 @@ +pub fn factor(a_sb: f64, a_bo: f64, t_b: f64, t_s: f64) -> f64 { + let numerator = a_sb.powf(2.0) * (t_b + 273.0); + let denominator = a_bo.powf(2.0) * (t_s + 273.0); + 1.0 + numerator / denominator +} + +#[cfg(not(coverage))] +pub fn factor_equation( + f_r: String, + a_sb: String, + t_b: String, + a_bo: String, + t_s: String, +) -> String { + format!( + "{} = 1 + \\frac{{ {}^2 \\times ( {} + 273 ) }}{{ {}^2 \\times ( {} + 273 ) }}", + f_r, a_sb, t_b, a_bo, t_s + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_factor() { + let result = factor(0.005, 0.005, 15.0, 5.0); + let expected = 2.035971223; + assert!((result - expected).abs() < 1e-6); + } +} \ No newline at end of file diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs new file mode 100644 index 0000000..b0a0a9f --- /dev/null +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs @@ -0,0 +1,31 @@ +pub fn factor(a_sb: f64, a_bo: f64, t_b: f64, t_s: f64) -> f64 { + let numerator = a_sb.powf(2.0) * (t_b + 273.0); + let denominator = a_bo.powf(2.0) * (t_s + 273.0); + 1.0 + numerator / denominator +} + +#[cfg(not(coverage))] +pub fn factor_equation( + f_r: String, + a_sb: String, + t_b: String, + a_bo: String, + t_s: String, +) -> String { + format!( + "{} = 1 + \\frac{{ {}^2 \\times ( {} + 273 ) }}{{ {}^2 \\times ( {} + 273 ) }}", + f_r, a_sb, t_b, a_bo, t_s + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_factor() { + let result = factor(0.005, 0.005, 15.0, 5.0); + let expected = 2.035971223; + assert!((result - expected).abs() < 1e-6); + } +} \ No newline at end of file From f9b86228de80d04c5c53ccc24a360be10cf1a4ae Mon Sep 17 00:00:00 2001 From: simonsantama Date: Sun, 11 Jan 2026 19:33:14 +0000 Subject: [PATCH 02/22] Adds equation to list of similar equations, including equation in cibse guide e --- crates/fire_dynamics_tools/src/lib.rs | 1 + .../src/similar_equations.rs | 46 +++++++++++++++++++ .../src/chapter_50/equation_50_18.rs | 31 +++++++------ 3 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 crates/fire_dynamics_tools/src/similar_equations.rs diff --git a/crates/fire_dynamics_tools/src/lib.rs b/crates/fire_dynamics_tools/src/lib.rs index d8eec22..e9e6aa1 100644 --- a/crates/fire_dynamics_tools/src/lib.rs +++ b/crates/fire_dynamics_tools/src/lib.rs @@ -3,3 +3,4 @@ pub mod chapter_2; pub mod chapter_4; pub mod chapter_5; pub mod chapter_9; +pub mod similar_equations; diff --git a/crates/fire_dynamics_tools/src/similar_equations.rs b/crates/fire_dynamics_tools/src/similar_equations.rs new file mode 100644 index 0000000..956995e --- /dev/null +++ b/crates/fire_dynamics_tools/src/similar_equations.rs @@ -0,0 +1,46 @@ +/// Similar equations grouped by functionality across different fire engineering standards. +/// This module provides cross-references to equations that calculate similar parameters +/// but may appear in different standards or use slightly different approaches. + +/// Fractional Effective Dose (FED) calculations +/// +/// These equations calculate the fractional effective dose, which represents +/// the accumulated toxic exposure over time as a fraction of the lethal dose. +pub mod fractional_effective_dose { + /// SFPE Handbook Chapter 50, Equation 50-18 + /// + /// Calculates FED using sum of concentration × time intervals divided by lethal concentration. + /// Formula: FED = Σ(c_i × Δt_i) / LC_t50 + /// + /// This implementation handles discrete time intervals with varying concentrations. + pub use sfpe_handbook::chapter_50::equation_50_18::fed as sfpe_fed; + + /// CIBSE Guide E Chapter 10, Equation 10-8 + /// + /// Calculates FED using mass flow rate × time divided by lethal concentration. + /// Formula: FED = (m_f × t) / LC_50 + /// + /// This implementation assumes constant mass flow rate over the exposure time. + pub use cibse_guide_e::chapter_10::equation_10_8::fractional_effective_dose as cibse_fed; + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_sfpe_fed() { + let c_i = vec![0.1, 0.2, 0.3]; + let delta_t_i = vec![1.0, 2.0, 3.0]; + let lc_t50 = 10.0; + let result = sfpe_fed(c_i, delta_t_i, lc_t50); + let expected = 0.14; // (0.1*1.0 + 0.2*2.0 + 0.3*3.0) / 10.0 + assert!((result - expected).abs() < 1e-6); + } + + #[test] + fn test_cibse_fed() { + let result = cibse_fed(2.0, 120.0, 1000.0); + assert_eq!(result, 0.24); + } + } +} \ No newline at end of file diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs index b0a0a9f..5b7bd9c 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs @@ -1,20 +1,18 @@ -pub fn factor(a_sb: f64, a_bo: f64, t_b: f64, t_s: f64) -> f64 { - let numerator = a_sb.powf(2.0) * (t_b + 273.0); - let denominator = a_bo.powf(2.0) * (t_s + 273.0); - 1.0 + numerator / denominator +pub fn fed(c_i: Vec, delta_t_i: Vec, lc_t50: f64) -> f64 { + let numerator: f64 = c_i.iter().zip(delta_t_i.iter()).map(|(c, dt)| c * dt).sum(); + numerator / lc_t50 } #[cfg(not(coverage))] -pub fn factor_equation( - f_r: String, - a_sb: String, - t_b: String, - a_bo: String, - t_s: String, +pub fn fed_equation( + fed: String, + c_i: String, + delta_t_i: String, + lc_t50: String, ) -> String { format!( - "{} = 1 + \\frac{{ {}^2 \\times ( {} + 273 ) }}{{ {}^2 \\times ( {} + 273 ) }}", - f_r, a_sb, t_b, a_bo, t_s + "{} = \\frac{{ \\sum {} \\times {} }}{{ {} }}", + fed, c_i, delta_t_i, lc_t50 ) } @@ -23,9 +21,12 @@ mod tests { use super::*; #[test] - fn test_factor() { - let result = factor(0.005, 0.005, 15.0, 5.0); - let expected = 2.035971223; + fn test_fed() { + let c_i = vec![0.1, 0.2, 0.3]; + let delta_t_i = vec![1.0, 2.0, 3.0]; + let lc_t50 = 10.0; + let result = fed(c_i, delta_t_i, lc_t50); + let expected = (0.1 * 1.0 + 0.2 * 2.0 + 0.3 * 3.0) / 10.0; // (0.1 + 0.4 + 0.9) / 10.0 = 0.14 assert!((result - expected).abs() < 1e-6); } } \ No newline at end of file From 4dfe7508fd788523a0a9261f3ace514bd27f638c Mon Sep 17 00:00:00 2001 From: simonsantama Date: Sun, 11 Jan 2026 19:41:13 +0000 Subject: [PATCH 03/22] Adds equation to list of similar equations, including equation in cibse guide e --- Cargo.lock | 4 ++ crates/fire_dynamics_tools/Cargo.toml | 4 ++ crates/fire_dynamics_tools/src/lib.rs | 1 - .../src/similar_equations.rs | 46 ------------------- .../docs/guide/equation-relationships.rst | 14 +++++- 5 files changed, 20 insertions(+), 49 deletions(-) delete mode 100644 crates/fire_dynamics_tools/src/similar_equations.rs diff --git a/Cargo.lock b/Cargo.lock index 50349c2..e60462c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,10 @@ version = "0.1.0" [[package]] name = "fire_dynamics_tools" version = "0.1.0" +dependencies = [ + "cibse_guide_e", + "sfpe_handbook", +] [[package]] name = "generic-array" diff --git a/crates/fire_dynamics_tools/Cargo.toml b/crates/fire_dynamics_tools/Cargo.toml index a5f5fcc..6fdbe9e 100644 --- a/crates/fire_dynamics_tools/Cargo.toml +++ b/crates/fire_dynamics_tools/Cargo.toml @@ -3,5 +3,9 @@ name = "fire_dynamics_tools" version = "0.1.0" edition = "2024" +[dependencies] +sfpe_handbook = { path = "../sfpe_handbook" } +cibse_guide_e = { path = "../cibse_guide_e" } + [lints] workspace = true \ No newline at end of file diff --git a/crates/fire_dynamics_tools/src/lib.rs b/crates/fire_dynamics_tools/src/lib.rs index e9e6aa1..d8eec22 100644 --- a/crates/fire_dynamics_tools/src/lib.rs +++ b/crates/fire_dynamics_tools/src/lib.rs @@ -3,4 +3,3 @@ pub mod chapter_2; pub mod chapter_4; pub mod chapter_5; pub mod chapter_9; -pub mod similar_equations; diff --git a/crates/fire_dynamics_tools/src/similar_equations.rs b/crates/fire_dynamics_tools/src/similar_equations.rs deleted file mode 100644 index 956995e..0000000 --- a/crates/fire_dynamics_tools/src/similar_equations.rs +++ /dev/null @@ -1,46 +0,0 @@ -/// Similar equations grouped by functionality across different fire engineering standards. -/// This module provides cross-references to equations that calculate similar parameters -/// but may appear in different standards or use slightly different approaches. - -/// Fractional Effective Dose (FED) calculations -/// -/// These equations calculate the fractional effective dose, which represents -/// the accumulated toxic exposure over time as a fraction of the lethal dose. -pub mod fractional_effective_dose { - /// SFPE Handbook Chapter 50, Equation 50-18 - /// - /// Calculates FED using sum of concentration × time intervals divided by lethal concentration. - /// Formula: FED = Σ(c_i × Δt_i) / LC_t50 - /// - /// This implementation handles discrete time intervals with varying concentrations. - pub use sfpe_handbook::chapter_50::equation_50_18::fed as sfpe_fed; - - /// CIBSE Guide E Chapter 10, Equation 10-8 - /// - /// Calculates FED using mass flow rate × time divided by lethal concentration. - /// Formula: FED = (m_f × t) / LC_50 - /// - /// This implementation assumes constant mass flow rate over the exposure time. - pub use cibse_guide_e::chapter_10::equation_10_8::fractional_effective_dose as cibse_fed; - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn test_sfpe_fed() { - let c_i = vec![0.1, 0.2, 0.3]; - let delta_t_i = vec![1.0, 2.0, 3.0]; - let lc_t50 = 10.0; - let result = sfpe_fed(c_i, delta_t_i, lc_t50); - let expected = 0.14; // (0.1*1.0 + 0.2*2.0 + 0.3*3.0) / 10.0 - assert!((result - expected).abs() < 1e-6); - } - - #[test] - fn test_cibse_fed() { - let result = cibse_fed(2.0, 120.0, 1000.0); - assert_eq!(result, 0.24); - } - } -} \ No newline at end of file diff --git a/crates/python_api/docs/guide/equation-relationships.rst b/crates/python_api/docs/guide/equation-relationships.rst index 6bb05c4..0788a5c 100644 --- a/crates/python_api/docs/guide/equation-relationships.rst +++ b/crates/python_api/docs/guide/equation-relationships.rst @@ -61,8 +61,18 @@ Visibility These equations calculate visibility distance through smoke but use different formulations: -- :func:`ofire.cibse_guide_e.chapter_10.equation_10_7.visibility` - CIBSE Guide E, Chapter 10, Equation 10.7 (uses extinction coefficient and optical density: k/(2.303×d)) -- :func:`ofire.fire_dynamics_tools.chapter_18.equation_18_1.visibility` - Fire Dynamics Tools, Chapter 18, Equation 18.1 (uses extinction coefficient, mass extinction coefficient, and particulate concentration: k/(αₘ×mₚ)) +- :func:`ofire.cibse_guide_e.chapter_10.equation_10_7.visibility` - CIBSE Guide E, Chapter 10, Equation 10.7 +- :func:`ofire.fire_dynamics_tools.chapter_18.equation_18_1.visibility` - Fire Dynamics Tools, Chapter 18, Equation 18.1 + +Fractional Effective Dose +~~~~~~~~~~~~~~~~~~~~~~~~~ + +*Group 3: Fractional Effective Dose Calculations* + +These equations calculate the fractional effective dose (FED), representing accumulated toxic exposure over time as a fraction of the lethal dose, but use different formulations: + +- :func:`ofire.sfpe_handbook.chapter_50.equation_50_18.fed` - SFPE Handbook, Chapter 50, Equation 50-18 +- :func:`ofire.cibse_guide_e.chapter_10.equation_10_8.fractional_effective_dose` - CIBSE Guide E, Chapter 10, Equation 10-8 Contributing to Equation Relationships --------------------------------------- From 7bfe11939848cc07ac40f0890890aeef9edcaf70 Mon Sep 17 00:00:00 2001 From: simonsantama Date: Sun, 11 Jan 2026 19:47:09 +0000 Subject: [PATCH 04/22] Finishes 50-18 for rust --- .../src/chapter_50/equation_50_18.rs | 17 ++++++----------- .../src/chapter_50/equation_50_19.rs | 2 +- .../src/chapter_50/equation_50_20.rs | 2 +- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs index 5b7bd9c..aba27fa 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs @@ -4,12 +4,7 @@ pub fn fed(c_i: Vec, delta_t_i: Vec, lc_t50: f64) -> f64 { } #[cfg(not(coverage))] -pub fn fed_equation( - fed: String, - c_i: String, - delta_t_i: String, - lc_t50: String, -) -> String { +pub fn fed_equation(fed: String, c_i: String, delta_t_i: String, lc_t50: String) -> String { format!( "{} = \\frac{{ \\sum {} \\times {} }}{{ {} }}", fed, c_i, delta_t_i, lc_t50 @@ -22,11 +17,11 @@ mod tests { #[test] fn test_fed() { - let c_i = vec![0.1, 0.2, 0.3]; - let delta_t_i = vec![1.0, 2.0, 3.0]; - let lc_t50 = 10.0; + let c_i = vec![0.001, 0.003]; + let delta_t_i = vec![1.0, 2.0]; + let lc_t50 = 0.015; let result = fed(c_i, delta_t_i, lc_t50); - let expected = (0.1 * 1.0 + 0.2 * 2.0 + 0.3 * 3.0) / 10.0; // (0.1 + 0.4 + 0.9) / 10.0 = 0.14 + let expected = 0.466666667; assert!((result - expected).abs() < 1e-6); } -} \ No newline at end of file +} diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs index b0a0a9f..7f839be 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs @@ -28,4 +28,4 @@ mod tests { let expected = 2.035971223; assert!((result - expected).abs() < 1e-6); } -} \ No newline at end of file +} diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs index b0a0a9f..7f839be 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs @@ -28,4 +28,4 @@ mod tests { let expected = 2.035971223; assert!((result - expected).abs() < 1e-6); } -} \ No newline at end of file +} From 6af661cee46c29b3b154d234856f47ac130a7482 Mon Sep 17 00:00:00 2001 From: simonsantama Date: Sun, 11 Jan 2026 19:57:50 +0000 Subject: [PATCH 05/22] Finishes 50-19 --- crates/python_api/docs/api/sfpe-handbook.rst | 8 ++++ .../src/sfpe_handbook/chapter_50.rs | 2 + .../chapter_50/equation_50_18.rs | 43 +++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs diff --git a/crates/python_api/docs/api/sfpe-handbook.rst b/crates/python_api/docs/api/sfpe-handbook.rst index 9ed1e63..8529b98 100644 --- a/crates/python_api/docs/api/sfpe-handbook.rst +++ b/crates/python_api/docs/api/sfpe-handbook.rst @@ -104,6 +104,14 @@ Equation 50.16 - Flow Area Factor for Pressurization Systems ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: ofire.sfpe_handbook.chapter_50.equation_50_16 + :members: + :undoc-members: + :show-inheritance: + +Equation 50.18 - Fractional Effective Dose +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: ofire.sfpe_handbook.chapter_50.equation_50_18 :members: :undoc-members: :show-inheritance: \ No newline at end of file diff --git a/crates/python_api/src/sfpe_handbook/chapter_50.rs b/crates/python_api/src/sfpe_handbook/chapter_50.rs index a4a7449..0091fa5 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50.rs @@ -2,6 +2,7 @@ pub mod equation_50_1; pub mod equation_50_14; pub mod equation_50_15; pub mod equation_50_16; +pub mod equation_50_18; pub mod equation_50_2; pub mod equation_50_4; pub mod equation_50_6; @@ -21,5 +22,6 @@ pub fn chapter_50(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(equation_50_14::equation_50_14))?; m.add_wrapped(wrap_pymodule!(equation_50_15::equation_50_15))?; m.add_wrapped(wrap_pymodule!(equation_50_16::equation_50_16))?; + m.add_wrapped(wrap_pymodule!(equation_50_18::equation_50_18))?; Ok(()) } diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs new file mode 100644 index 0000000..c3362a3 --- /dev/null +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs @@ -0,0 +1,43 @@ +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +use openfire::sfpe_handbook::chapter_50::equation_50_18 as rust_equation_50_18; + +#[pyfunction] +/// Fractional Effective Dose (FED) calculation for for evaluation exposure to smoke. +/// +/// .. math:: +/// +/// FED = \frac{\sum (C_i \Delta t_i)}{LC_{t50}} +/// +/// where: +/// +/// - :math:`FED` is the fractional effective dose (dimensionless) +/// - :math:`C_i` is the mass concentration of material burned at the end of time interval i (g/m³) +/// - :math:`\Delta t_i` is the time interval i (s) +/// - :math:`LC_{t50}` is the lethal exposure dose from test data (mg/m³ or ppm) +/// +/// Args: +/// c_i (list[float]): Concentration values at each time interval (g/m³) +/// delta_t_i (list[float]): Time intervals (s) +/// lc_t50 (float): Lethal exposure dose from test data (mg/m³ or ppm) +/// +/// Returns: +/// float: Fractional effective dose (dimensionless) +/// +/// Example: +/// >>> import ofire +/// >>> c_i = [0.001, 0.002, 0.003] +/// >>> delta_t_i = [1.0, 5.0, 1.0] +/// >>> lc_t50 = 10.0 +/// >>> result = ofire.sfpe_handbook.chapter_50.equation_50_18.fed(c_i, delta_t_i, lc_t50) +/// >>> print(f"{result:.2f}") +fn fed(c_i: Vec, delta_t_i: Vec, lc_t50: f64) -> PyResult { + Ok(rust_equation_50_18::fed(c_i, delta_t_i, lc_t50)) +} + +#[pymodule] +pub fn equation_50_18(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(fed, m)?)?; + Ok(()) +} \ No newline at end of file From a088e07e780e3fa9f01e2c5edfea41ce75808910 Mon Sep 17 00:00:00 2001 From: simonsantama Date: Sun, 11 Jan 2026 20:02:25 +0000 Subject: [PATCH 06/22] Finishes 50-18 --- .../src/sfpe_handbook/chapter_50/equation_50_18.rs | 8 +++++++- crates/sfpe_handbook/src/chapter_50/equation_50_18.rs | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs index c3362a3..7d71e03 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs @@ -25,10 +25,16 @@ use openfire::sfpe_handbook::chapter_50::equation_50_18 as rust_equation_50_18; /// Returns: /// float: Fractional effective dose (dimensionless) /// +/// Assumptions: +/// Uniform time intervals. +/// +/// Limitations: +/// Simplest model for evaluating exposure to smoke. +/// /// Example: /// >>> import ofire /// >>> c_i = [0.001, 0.002, 0.003] -/// >>> delta_t_i = [1.0, 5.0, 1.0] +/// >>> delta_t_i = [1.0, 1.0, 1.0] /// >>> lc_t50 = 10.0 /// >>> result = ofire.sfpe_handbook.chapter_50.equation_50_18.fed(c_i, delta_t_i, lc_t50) /// >>> print(f"{result:.2f}") diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs index aba27fa..8027837 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs @@ -18,10 +18,10 @@ mod tests { #[test] fn test_fed() { let c_i = vec![0.001, 0.003]; - let delta_t_i = vec![1.0, 2.0]; + let delta_t_i = vec![1.0, 1.0]; let lc_t50 = 0.015; let result = fed(c_i, delta_t_i, lc_t50); - let expected = 0.466666667; + let expected = 0.266666667; assert!((result - expected).abs() < 1e-6); } } From 4e648df11506d54c3ca0302faace9ae1969d989b Mon Sep 17 00:00:00 2001 From: Simon Santamaria Date: Tue, 13 Jan 2026 20:45:13 +0000 Subject: [PATCH 07/22] Update crates/fire_dynamics_tools/Cargo.toml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/fire_dynamics_tools/Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/fire_dynamics_tools/Cargo.toml b/crates/fire_dynamics_tools/Cargo.toml index 6fdbe9e..a5f5fcc 100644 --- a/crates/fire_dynamics_tools/Cargo.toml +++ b/crates/fire_dynamics_tools/Cargo.toml @@ -3,9 +3,5 @@ name = "fire_dynamics_tools" version = "0.1.0" edition = "2024" -[dependencies] -sfpe_handbook = { path = "../sfpe_handbook" } -cibse_guide_e = { path = "../cibse_guide_e" } - [lints] workspace = true \ No newline at end of file From d7e2113f729c359771e841b53c7f4894df314328 Mon Sep 17 00:00:00 2001 From: simonsantama Date: Thu, 15 Jan 2026 11:35:57 +0000 Subject: [PATCH 08/22] Implements 50-19 --- Cargo.lock | 4 -- crates/python_api/docs/api/sfpe-handbook.rst | 8 ++++ .../src/sfpe_handbook/chapter_50.rs | 2 + .../chapter_50/equation_50_18.rs | 2 +- .../chapter_50/equation_50_19.rs | 46 +++++++++++++++++++ .../src/chapter_50/equation_50_18.rs | 12 ++--- .../src/chapter_50/equation_50_19.rs | 27 +++++------ 7 files changed, 75 insertions(+), 26 deletions(-) create mode 100644 crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs diff --git a/Cargo.lock b/Cargo.lock index e60462c..50349c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,10 +83,6 @@ version = "0.1.0" [[package]] name = "fire_dynamics_tools" version = "0.1.0" -dependencies = [ - "cibse_guide_e", - "sfpe_handbook", -] [[package]] name = "generic-array" diff --git a/crates/python_api/docs/api/sfpe-handbook.rst b/crates/python_api/docs/api/sfpe-handbook.rst index 8529b98..3e5d132 100644 --- a/crates/python_api/docs/api/sfpe-handbook.rst +++ b/crates/python_api/docs/api/sfpe-handbook.rst @@ -112,6 +112,14 @@ Equation 50.18 - Fractional Effective Dose ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: ofire.sfpe_handbook.chapter_50.equation_50_18 + :members: + :undoc-members: + :show-inheritance: + +Equation 50.19 - Visibility Through Uniform Smoke +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: ofire.sfpe_handbook.chapter_50.equation_50_19 :members: :undoc-members: :show-inheritance: \ No newline at end of file diff --git a/crates/python_api/src/sfpe_handbook/chapter_50.rs b/crates/python_api/src/sfpe_handbook/chapter_50.rs index 0091fa5..98dcbac 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50.rs @@ -3,6 +3,7 @@ pub mod equation_50_14; pub mod equation_50_15; pub mod equation_50_16; pub mod equation_50_18; +pub mod equation_50_19; pub mod equation_50_2; pub mod equation_50_4; pub mod equation_50_6; @@ -23,5 +24,6 @@ pub fn chapter_50(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(equation_50_15::equation_50_15))?; m.add_wrapped(wrap_pymodule!(equation_50_16::equation_50_16))?; m.add_wrapped(wrap_pymodule!(equation_50_18::equation_50_18))?; + m.add_wrapped(wrap_pymodule!(equation_50_19::equation_50_19))?; Ok(()) } diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs index 7d71e03..6aa75e5 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs @@ -38,7 +38,7 @@ use openfire::sfpe_handbook::chapter_50::equation_50_18 as rust_equation_50_18; /// >>> lc_t50 = 10.0 /// >>> result = ofire.sfpe_handbook.chapter_50.equation_50_18.fed(c_i, delta_t_i, lc_t50) /// >>> print(f"{result:.2f}") -fn fed(c_i: Vec, delta_t_i: Vec, lc_t50: f64) -> PyResult { +fn fed(c_i: Vec, delta_t_i: f64, lc_t50: f64) -> PyResult { Ok(rust_equation_50_18::fed(c_i, delta_t_i, lc_t50)) } diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs new file mode 100644 index 0000000..2263471 --- /dev/null +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs @@ -0,0 +1,46 @@ +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +use openfire::sfpe_handbook::chapter_50::equation_50_19 as rust_equation_50_19; + +#[pyfunction] +/// Visibility in smoke at a point where mass concentration of fuel burned is known. +/// +/// .. math:: +/// +/// S_i = \frac{K}{2.303 \times \delta m \times C_i} +/// +/// where: +/// +/// - :math:`S_i` is the visibility through smoke (m) +/// - :math:`K` is the Bouguer-Beer law constant (dimensionless) +/// - :math:`\delta m` is the mass extinction coefficient (m²/g) +/// - :math:`C_i` is the mass concentration of particulates (g/m³) +/// +/// Args: +/// k (float): Bouguer-Beer law constant (dimensionless) +/// delta_m (float): Mass extinction coefficient (m²/g) +/// c_i (float): Mass concentration of particulates (g/m³) +/// +/// Returns: +/// float: Visibility through smoke (m) +/// +/// Assumptions: +/// The calculated visibility can be thought of as visibility if smoke is uniform. +/// +/// Limitations: +/// See assumptions above. Assumes uniform smoke and, utilises the proportionality constant K, commonly taken as 8 for illumnated signs and 3 for non-illuminated. +/// +/// Example: +/// >>> import ofire +/// >>> result = ofire.sfpe_handbook.chapter_50.equation_50_19.visibility(8.0, 0.22, 1.0) +/// >>> print(f"{result:.2f}") +fn visibility(k: f64, delta_m: f64, c_i: f64) -> PyResult { + Ok(rust_equation_50_19::visibility(k, delta_m, c_i)) +} + +#[pymodule] +pub fn equation_50_19(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(visibility, m)?)?; + Ok(()) +} \ No newline at end of file diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs index 8027837..f4899fd 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs @@ -1,13 +1,13 @@ -pub fn fed(c_i: Vec, delta_t_i: Vec, lc_t50: f64) -> f64 { - let numerator: f64 = c_i.iter().zip(delta_t_i.iter()).map(|(c, dt)| c * dt).sum(); +pub fn fed(c_i: Vec, delta_t: f64, lc_t50: f64) -> f64 { + let numerator: f64 = c_i.iter().map(|c| c * delta_t).sum(); numerator / lc_t50 } #[cfg(not(coverage))] -pub fn fed_equation(fed: String, c_i: String, delta_t_i: String, lc_t50: String) -> String { +pub fn fed_equation(fed: String, c_i: String, delta_t: String, lc_t50: String) -> String { format!( "{} = \\frac{{ \\sum {} \\times {} }}{{ {} }}", - fed, c_i, delta_t_i, lc_t50 + fed, c_i, delta_t, lc_t50 ) } @@ -18,9 +18,9 @@ mod tests { #[test] fn test_fed() { let c_i = vec![0.001, 0.003]; - let delta_t_i = vec![1.0, 1.0]; + let delta_t = 1.0; let lc_t50 = 0.015; - let result = fed(c_i, delta_t_i, lc_t50); + let result = fed(c_i, delta_t, lc_t50); let expected = 0.266666667; assert!((result - expected).abs() < 1e-6); } diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs index 7f839be..3818d81 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs @@ -1,20 +1,17 @@ -pub fn factor(a_sb: f64, a_bo: f64, t_b: f64, t_s: f64) -> f64 { - let numerator = a_sb.powf(2.0) * (t_b + 273.0); - let denominator = a_bo.powf(2.0) * (t_s + 273.0); - 1.0 + numerator / denominator +pub fn visibility(k: f64, delta_m: f64, c_i: f64) -> f64 { + k / (2.303 * delta_m * c_i) } #[cfg(not(coverage))] -pub fn factor_equation( - f_r: String, - a_sb: String, - t_b: String, - a_bo: String, - t_s: String, +pub fn visibility_equation( + s_i: String, + k: String, + delta_m: String, + c_i: String, ) -> String { format!( - "{} = 1 + \\frac{{ {}^2 \\times ( {} + 273 ) }}{{ {}^2 \\times ( {} + 273 ) }}", - f_r, a_sb, t_b, a_bo, t_s + "{} = \\frac{{{}}}{{2.303 \\times {} \\times {}}}", + s_i, k, delta_m, c_i, ) } @@ -23,9 +20,9 @@ mod tests { use super::*; #[test] - fn test_factor() { - let result = factor(0.005, 0.005, 15.0, 5.0); - let expected = 2.035971223; + fn test_visibility() { + let result = visibility(8.0, 0.22, 1.0); + let expected = 15.78968144; assert!((result - expected).abs() < 1e-6); } } From aabe1005a606d9147192ce54335b3c01f1f61a16 Mon Sep 17 00:00:00 2001 From: simonsantama Date: Thu, 15 Jan 2026 11:50:00 +0000 Subject: [PATCH 09/22] Start of 50-20 --- .../src/chapter_50/equation_50_20.rs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs index 7f839be..3818d81 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs @@ -1,20 +1,17 @@ -pub fn factor(a_sb: f64, a_bo: f64, t_b: f64, t_s: f64) -> f64 { - let numerator = a_sb.powf(2.0) * (t_b + 273.0); - let denominator = a_bo.powf(2.0) * (t_s + 273.0); - 1.0 + numerator / denominator +pub fn visibility(k: f64, delta_m: f64, c_i: f64) -> f64 { + k / (2.303 * delta_m * c_i) } #[cfg(not(coverage))] -pub fn factor_equation( - f_r: String, - a_sb: String, - t_b: String, - a_bo: String, - t_s: String, +pub fn visibility_equation( + s_i: String, + k: String, + delta_m: String, + c_i: String, ) -> String { format!( - "{} = 1 + \\frac{{ {}^2 \\times ( {} + 273 ) }}{{ {}^2 \\times ( {} + 273 ) }}", - f_r, a_sb, t_b, a_bo, t_s + "{} = \\frac{{{}}}{{2.303 \\times {} \\times {}}}", + s_i, k, delta_m, c_i, ) } @@ -23,9 +20,9 @@ mod tests { use super::*; #[test] - fn test_factor() { - let result = factor(0.005, 0.005, 15.0, 5.0); - let expected = 2.035971223; + fn test_visibility() { + let result = visibility(8.0, 0.22, 1.0); + let expected = 15.78968144; assert!((result - expected).abs() < 1e-6); } } From f5d17cd197f1d5de14ffe1b9096b57d256a0a0e1 Mon Sep 17 00:00:00 2001 From: simonsantama Date: Thu, 15 Jan 2026 12:19:25 +0000 Subject: [PATCH 10/22] Implements rust aspect of 50-20 --- .../chapter_50/equation_50_18.rs | 2 +- .../chapter_50/equation_50_19.rs | 2 +- .../src/chapter_50/equation_50_19.rs | 9 ++------- .../src/chapter_50/equation_50_20.rs | 19 +++++++------------ 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs index 6aa75e5..05ad3e0 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs @@ -46,4 +46,4 @@ fn fed(c_i: Vec, delta_t_i: f64, lc_t50: f64) -> PyResult { pub fn equation_50_18(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(fed, m)?)?; Ok(()) -} \ No newline at end of file +} diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs index 2263471..775deb0 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs @@ -43,4 +43,4 @@ fn visibility(k: f64, delta_m: f64, c_i: f64) -> PyResult { pub fn equation_50_19(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(visibility, m)?)?; Ok(()) -} \ No newline at end of file +} diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs index 3818d81..142fc6f 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs @@ -3,15 +3,10 @@ pub fn visibility(k: f64, delta_m: f64, c_i: f64) -> f64 { } #[cfg(not(coverage))] -pub fn visibility_equation( - s_i: String, - k: String, - delta_m: String, - c_i: String, -) -> String { +pub fn visibility_equation(s_i: String, k: String, delta_m: String, c_i: String) -> String { format!( "{} = \\frac{{{}}}{{2.303 \\times {} \\times {}}}", - s_i, k, delta_m, c_i, + s_i, k, delta_m, c_i, ) } diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs index 3818d81..2ce827e 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs @@ -1,17 +1,12 @@ -pub fn visibility(k: f64, delta_m: f64, c_i: f64) -> f64 { - k / (2.303 * delta_m * c_i) +pub fn visibility(k: f64, l: f64, lambda: f64) -> f64 { + -k * l / ((1.0 - lambda / 100.0).ln()) } #[cfg(not(coverage))] -pub fn visibility_equation( - s_i: String, - k: String, - delta_m: String, - c_i: String, -) -> String { +pub fn visibility_equation(s_i: String, k: String, l: String, lambda: String) -> String { format!( - "{} = \\frac{{{}}}{{2.303 \\times {} \\times {}}}", - s_i, k, delta_m, c_i, + "{} = \\frac{{{}}}{{\\ln(1 - \\frac{{{}}}{{100}})}}", + s_i, k, lambda, ) } @@ -21,8 +16,8 @@ mod tests { #[test] fn test_visibility() { - let result = visibility(8.0, 0.22, 1.0); - let expected = 15.78968144; + let result = visibility(8.0, 10.0, 95.0); + let expected = 26.70465606; assert!((result - expected).abs() < 1e-6); } } From 8b3f07d8425c3f56a1a24ceefe6ad4b4fb72d9b5 Mon Sep 17 00:00:00 2001 From: simonsantama Date: Thu, 15 Jan 2026 12:25:30 +0000 Subject: [PATCH 11/22] Finishes first implementation of 50-20 --- crates/python_api/docs/api/sfpe-handbook.rst | 7 +++ .../src/sfpe_handbook/chapter_50.rs | 2 + .../chapter_50/equation_50_20.rs | 46 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 crates/python_api/src/sfpe_handbook/chapter_50/equation_50_20.rs diff --git a/crates/python_api/docs/api/sfpe-handbook.rst b/crates/python_api/docs/api/sfpe-handbook.rst index 3e5d132..fbb9437 100644 --- a/crates/python_api/docs/api/sfpe-handbook.rst +++ b/crates/python_api/docs/api/sfpe-handbook.rst @@ -120,6 +120,13 @@ Equation 50.19 - Visibility Through Uniform Smoke ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: ofire.sfpe_handbook.chapter_50.equation_50_19 + :members: + :undoc-members: + :show-inheritance: +Equation 50.20 - Visibility Through Smoke (Percent Obscuration) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: ofire.sfpe_handbook.chapter_50.equation_50_20 :members: :undoc-members: :show-inheritance: \ No newline at end of file diff --git a/crates/python_api/src/sfpe_handbook/chapter_50.rs b/crates/python_api/src/sfpe_handbook/chapter_50.rs index 98dcbac..9d0a733 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50.rs @@ -5,6 +5,7 @@ pub mod equation_50_16; pub mod equation_50_18; pub mod equation_50_19; pub mod equation_50_2; +pub mod equation_50_20; pub mod equation_50_4; pub mod equation_50_6; pub mod equation_50_7; @@ -25,5 +26,6 @@ pub fn chapter_50(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(equation_50_16::equation_50_16))?; m.add_wrapped(wrap_pymodule!(equation_50_18::equation_50_18))?; m.add_wrapped(wrap_pymodule!(equation_50_19::equation_50_19))?; + m.add_wrapped(wrap_pymodule!(equation_50_20::equation_50_20))?; Ok(()) } diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_20.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_20.rs new file mode 100644 index 0000000..1216d0a --- /dev/null +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_20.rs @@ -0,0 +1,46 @@ +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +use openfire::sfpe_handbook::chapter_50::equation_50_20 as rust_equation_50_20; + +#[pyfunction] +/// Visibility calculation through smoke from percent obscuration. +/// +/// .. math:: +/// +/// S_i = -\frac{K \times L}{\ln(1 - \frac{\lambda}{100})} +/// +/// where: +/// +/// - :math:`S_i` is the visibility through smoke (m) +/// - :math:`K` is proportionality constant (dimensionless) +/// - :math:`L` is the path length (m) +/// - :math:`\lambda` is the percent obscuration (dimensionless) +/// +/// Args: +/// k (float): Proportionality constant (dimensionless) +/// l (float): Path length (m) +/// lambda (float): Percent obscuration (dimensionless) +/// +/// Returns: +/// float: Visibility through smoke (m) +/// +/// Assumptions: +/// An object can be seen for S > L. +/// +/// Limitations: +/// None stated. +/// +/// Example: +/// >>> import ofire +/// >>> result = ofire.sfpe_handbook.chapter_50.equation_50_20.visibility(8.0, 10.0, 95.0) +/// >>> print(f"{result:.2f}") +fn visibility(k: f64, l: f64, lambda: f64) -> PyResult { + Ok(rust_equation_50_20::visibility(k, l, lambda)) +} + +#[pymodule] +pub fn equation_50_20(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(visibility, m)?)?; + Ok(()) +} \ No newline at end of file From 61e27a9e24e5057b7776d1bfd98b605ad51f47cb Mon Sep 17 00:00:00 2001 From: simonsantama Date: Thu, 15 Jan 2026 12:31:45 +0000 Subject: [PATCH 12/22] Minor changes to fix issues with docs --- .../src/sfpe_handbook/chapter_50/equation_50_18.rs | 4 ++-- .../src/sfpe_handbook/chapter_50/equation_50_19.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs index 05ad3e0..a022cb3 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs @@ -4,7 +4,7 @@ use pyo3::wrap_pyfunction; use openfire::sfpe_handbook::chapter_50::equation_50_18 as rust_equation_50_18; #[pyfunction] -/// Fractional Effective Dose (FED) calculation for for evaluation exposure to smoke. +/// Fractional Effective Dose (FED) calculation for evaluation of exposure to smoke. /// /// .. math:: /// @@ -19,7 +19,7 @@ use openfire::sfpe_handbook::chapter_50::equation_50_18 as rust_equation_50_18; /// /// Args: /// c_i (list[float]): Concentration values at each time interval (g/m³) -/// delta_t_i (list[float]): Time intervals (s) +/// delta_t_i (float): Time intervals (s) /// lc_t50 (float): Lethal exposure dose from test data (mg/m³ or ppm) /// /// Returns: diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs index 775deb0..b3fe908 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs @@ -13,14 +13,14 @@ use openfire::sfpe_handbook::chapter_50::equation_50_19 as rust_equation_50_19; /// where: /// /// - :math:`S_i` is the visibility through smoke (m) -/// - :math:`K` is the Bouguer-Beer law constant (dimensionless) -/// - :math:`\delta m` is the mass extinction coefficient (m²/g) -/// - :math:`C_i` is the mass concentration of particulates (g/m³) +/// - :math:`K` is the proportionality constant (dimensionless) +/// - :math:`\delta m` is the mass optical density (m²/g) +/// - :math:`C_i` is the mass concentration of fuel burned (g/m³) /// /// Args: -/// k (float): Bouguer-Beer law constant (dimensionless) -/// delta_m (float): Mass extinction coefficient (m²/g) -/// c_i (float): Mass concentration of particulates (g/m³) +/// k (float): Proportionality constant (dimensionless) +/// delta_m (float): Mass optical density (m²/g) +/// c_i (float): Mass concentration of fuel burned (g/m³) /// /// Returns: /// float: Visibility through smoke (m) From d2d6e926b981e4444ce041322126fb28c73304fb Mon Sep 17 00:00:00 2001 From: Simon Santamaria Date: Thu, 15 Jan 2026 12:35:17 +0000 Subject: [PATCH 13/22] Update crates/python_api/docs/api/sfpe-handbook.rst Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/python_api/docs/api/sfpe-handbook.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/python_api/docs/api/sfpe-handbook.rst b/crates/python_api/docs/api/sfpe-handbook.rst index fbb9437..b44b787 100644 --- a/crates/python_api/docs/api/sfpe-handbook.rst +++ b/crates/python_api/docs/api/sfpe-handbook.rst @@ -124,7 +124,7 @@ Equation 50.19 - Visibility Through Uniform Smoke :undoc-members: :show-inheritance: Equation 50.20 - Visibility Through Smoke (Percent Obscuration) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: ofire.sfpe_handbook.chapter_50.equation_50_20 :members: From 3d9423b6ce49b394be20c80879084a0348c82097 Mon Sep 17 00:00:00 2001 From: Simon Santamaria Date: Thu, 15 Jan 2026 12:41:00 +0000 Subject: [PATCH 14/22] Update crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs index b3fe908..e008a9f 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_19.rs @@ -29,7 +29,7 @@ use openfire::sfpe_handbook::chapter_50::equation_50_19 as rust_equation_50_19; /// The calculated visibility can be thought of as visibility if smoke is uniform. /// /// Limitations: -/// See assumptions above. Assumes uniform smoke and, utilises the proportionality constant K, commonly taken as 8 for illumnated signs and 3 for non-illuminated. +/// See assumptions above. Assumes uniform smoke and, utilises the proportionality constant K, commonly taken as 8 for illuminated signs and 3 for non-illuminated. /// /// Example: /// >>> import ofire From 68205018bf0360d65e1abca504bcc3e36006ff22 Mon Sep 17 00:00:00 2001 From: Simon Santamaria Date: Thu, 15 Jan 2026 12:41:43 +0000 Subject: [PATCH 15/22] Update crates/sfpe_handbook/src/chapter_50/equation_50_20.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/sfpe_handbook/src/chapter_50/equation_50_20.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs index 2ce827e..b9ff493 100644 --- a/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs @@ -5,8 +5,8 @@ pub fn visibility(k: f64, l: f64, lambda: f64) -> f64 { #[cfg(not(coverage))] pub fn visibility_equation(s_i: String, k: String, l: String, lambda: String) -> String { format!( - "{} = \\frac{{{}}}{{\\ln(1 - \\frac{{{}}}{{100}})}}", - s_i, k, lambda, + "{} = \\frac{{{} \\times {}}}{{\\ln(1 - \\frac{{{}}}{{100}})}}", + s_i, k, l, lambda, ) } From cf1b2edb64eb5e4c9b6f4a23bfd246e40b72255a Mon Sep 17 00:00:00 2001 From: simonsantama Date: Thu, 15 Jan 2026 12:43:14 +0000 Subject: [PATCH 16/22] fixes error in example --- .../python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs index a022cb3..56c2170 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs @@ -34,7 +34,7 @@ use openfire::sfpe_handbook::chapter_50::equation_50_18 as rust_equation_50_18; /// Example: /// >>> import ofire /// >>> c_i = [0.001, 0.002, 0.003] -/// >>> delta_t_i = [1.0, 1.0, 1.0] +/// >>> delta_t_i = 1.0 /// >>> lc_t50 = 10.0 /// >>> result = ofire.sfpe_handbook.chapter_50.equation_50_18.fed(c_i, delta_t_i, lc_t50) /// >>> print(f"{result:.2f}") From 3821a442c39354acb029cff70d409c27ffc7a3fe Mon Sep 17 00:00:00 2001 From: simonsantama Date: Thu, 15 Jan 2026 20:28:45 +0000 Subject: [PATCH 17/22] Adds examples 2 in chapter 50 --- .../docs/examples/sfpe-handbook-examples.rst | 14 +-- .../examples/sfpe-handbook/chapter-50.rst | 100 ++++++++++++++++++ 2 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst diff --git a/crates/python_api/docs/examples/sfpe-handbook-examples.rst b/crates/python_api/docs/examples/sfpe-handbook-examples.rst index 4bb1887..937a3da 100644 --- a/crates/python_api/docs/examples/sfpe-handbook-examples.rst +++ b/crates/python_api/docs/examples/sfpe-handbook-examples.rst @@ -1,16 +1,6 @@ SFPE Handbook ============= -This page will contain practical examples using SFPE Handbook calculations. +This page contains practical examples using SFPE Handbook calculations, organized by chapter. -Coming Soon ------------ - -Examples for SFPE Handbook calculations will be added as the Python API is expanded to include more SFPE Handbook modules. - -.. code-block:: python - - import ofire - - # Example: SFPE Handbook calculations - # (Examples will be added as modules are implemented) \ No newline at end of file +.. include:: sfpe-handbook/chapter-50.rst \ No newline at end of file diff --git a/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst b/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst new file mode 100644 index 0000000..f7f43ae --- /dev/null +++ b/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst @@ -0,0 +1,100 @@ +Chapter 50 +========== + +Example 2: Door Opening Force +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + """ + OpenFire Example — SFPE Handbook Chapter 50 + Topic: Door opening force for a side-hinged swinging door (Eq. 50.14) + + Unit policy: + - All calculations performed in SI units (m, Pa, N). + - Inputs may be provided in non-SI, but are converted explicitly before use. + + Given conversions (per user requirement): + - 1 ft = 0.3048 m + - 1 in = 0.0254 m + - 1 in H2O = 249.09 Pa + + Additional necessary conversion: + - 1 lbf = 4.4482216152605 N + """ + + import ofire + + + # ============================================================================= + # 1) Unit conversions (explicit) + # ============================================================================= + FT_TO_M = 0.3048 + IN_TO_M = 0.0254 + IN_H2O_TO_PA = 249.09 + LBF_TO_N = 4.4482216152605 + + + # ============================================================================= + # 2) Inputs (as given in the problem statement) + # ============================================================================= + door_width_ft = 3.0 + door_height_ft = 7.0 + closer_force_lbf = 9.0 + delta_p_in_h2o = 0.35 + knob_offset_from_edge_in = 3.0 # knob is 3 in from the door edge (knob side) + + + # ============================================================================= + # 3) Convert inputs to SI (SI-only variables used in calculations) + # ============================================================================= + W_m = door_width_ft * FT_TO_M + H_m = door_height_ft * FT_TO_M + A_m2 = W_m * H_m + + F_dc_N = closer_force_lbf * LBF_TO_N + delta_p_Pa = delta_p_in_h2o * IN_H2O_TO_PA + d_m = knob_offset_from_edge_in * IN_TO_M + + + # ============================================================================= + # 4) Calculation (OpenFire — SFPE HB Ch.50 Eq. 50.14) + # ============================================================================= + F_open_N = ofire.sfpe_handbook.chapter_50.equation_50_14.door_opening_force( + f_dc=F_dc_N, + w=W_m, + a=A_m2, + delta_p=delta_p_Pa, + d=d_m, + ) + + + # ============================================================================= + # 5) Print inputs and results (always) + # ============================================================================= + print("=" * 72) + print("OpenFire — SFPE Handbook Ch.50 — Door Opening Force (Eq. 50.14)") + print("=" * 72) + + print("\nINPUTS (original, as stated):") + print(f" Door width = {door_width_ft:.3f} ft") + print(f" Door height = {door_height_ft:.3f} ft") + print(f" Door closer force = {closer_force_lbf:.3f} lbf") + print(f" Pressure difference, Δp = {delta_p_in_h2o:.3f} in H2O") + print(f" Knob offset from edge = {knob_offset_from_edge_in:.3f} in") + + print("\nINPUTS (converted to SI; used in calculation):") + print(f" Door width, W = {W_m:.6f} m") + print(f" Door height, H = {H_m:.6f} m") + print(f" Door area, A = {A_m2:.6f} m^2") + print(f" Door closer force, F_dc = {F_dc_N:.6f} N") + print(f" Pressure difference, Δp = {delta_p_Pa:.6f} Pa") + print(f" Knob offset, d = {d_m:.6f} m") + + print("\nRESULTS (SI):") + print(f" Total door opening force, F = {F_open_N:.6f} N") + + print("\nNotes:") + print(" - All calculations performed in SI units (m, Pa, N).") + print(" - OpenFire function: ofire.sfpe_handbook.chapter_50.equation_50_14.door_opening_force") + print("=" * 72) \ No newline at end of file From e859eb77976b562521e5f7eb58f6b31fbdbba815 Mon Sep 17 00:00:00 2001 From: Jamie Maclean Date: Thu, 15 Jan 2026 21:44:22 +0100 Subject: [PATCH 18/22] fix left navigation parent child relationships --- crates/python_api/docs/examples/index.rst | 2 +- crates/python_api/docs/examples/sfpe-handbook-examples.rst | 5 ++++- crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/python_api/docs/examples/index.rst b/crates/python_api/docs/examples/index.rst index a3f3403..01a0e9f 100644 --- a/crates/python_api/docs/examples/index.rst +++ b/crates/python_api/docs/examples/index.rst @@ -9,7 +9,7 @@ Available Example Categories ---------------------------- .. toctree:: - :maxdepth: 1 + :maxdepth: 3 thermal-radiation-examples fire-dynamics-examples diff --git a/crates/python_api/docs/examples/sfpe-handbook-examples.rst b/crates/python_api/docs/examples/sfpe-handbook-examples.rst index 937a3da..64d61ce 100644 --- a/crates/python_api/docs/examples/sfpe-handbook-examples.rst +++ b/crates/python_api/docs/examples/sfpe-handbook-examples.rst @@ -3,4 +3,7 @@ SFPE Handbook This page contains practical examples using SFPE Handbook calculations, organized by chapter. -.. include:: sfpe-handbook/chapter-50.rst \ No newline at end of file +.. toctree:: + :maxdepth: 2 + + sfpe-handbook/chapter-50 \ No newline at end of file diff --git a/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst b/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst index f7f43ae..e36df4c 100644 --- a/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst +++ b/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst @@ -1,5 +1,5 @@ Chapter 50 -========== +---------- Example 2: Door Opening Force ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From ac57f6b4e64488be18277db8214bdea7e92bf924 Mon Sep 17 00:00:00 2001 From: Jamie Maclean Date: Thu, 15 Jan 2026 21:47:41 +0100 Subject: [PATCH 19/22] remove from left navigation --- crates/python_api/docs/examples/sfpe-handbook-examples.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/python_api/docs/examples/sfpe-handbook-examples.rst b/crates/python_api/docs/examples/sfpe-handbook-examples.rst index 64d61ce..937a3da 100644 --- a/crates/python_api/docs/examples/sfpe-handbook-examples.rst +++ b/crates/python_api/docs/examples/sfpe-handbook-examples.rst @@ -3,7 +3,4 @@ SFPE Handbook This page contains practical examples using SFPE Handbook calculations, organized by chapter. -.. toctree:: - :maxdepth: 2 - - sfpe-handbook/chapter-50 \ No newline at end of file +.. include:: sfpe-handbook/chapter-50.rst \ No newline at end of file From ef87d160c087c45d2691c736081921dfa87dfaef Mon Sep 17 00:00:00 2001 From: simonsantama Date: Fri, 16 Jan 2026 17:15:54 +0000 Subject: [PATCH 20/22] Intermediate commit --- .../examples/sfpe-handbook/chapter-50.rst | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst b/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst index e36df4c..d301c17 100644 --- a/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst +++ b/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst @@ -97,4 +97,31 @@ Example 2: Door Opening Force print("\nNotes:") print(" - All calculations performed in SI units (m, Pa, N).") print(" - OpenFire function: ofire.sfpe_handbook.chapter_50.equation_50_14.door_opening_force") - print("=" * 72) \ No newline at end of file + print("=" * 72) + + +Example 3: Simple Stairwell Pressurisation in a Simple Building +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Part 1 +^^^^^^ + +.. code-block:: python + + # Code to be added soon + + +Part 2 +^^^^^^ + +.. code-block:: python + + # Code to be added soon + + +Part 3 +^^^^^^ + +.. code-block:: python + + # Code to be added soon \ No newline at end of file From 40b060eb0662a62a55f7c6a764639dafc20721dc Mon Sep 17 00:00:00 2001 From: simonsantama Date: Fri, 16 Jan 2026 20:00:40 +0000 Subject: [PATCH 21/22] Finishes examples of chapter 50 --- .../examples/sfpe-handbook/chapter-50.rst | 308 +++++++++++++++++- 1 file changed, 302 insertions(+), 6 deletions(-) diff --git a/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst b/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst index d301c17..95f66f2 100644 --- a/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst +++ b/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst @@ -108,7 +108,162 @@ Part 1 .. code-block:: python - # Code to be added soon + """ + OpenFire Example — SFPE Handbook Chapter 50 (Smoke Control) + Example 3 (Part 1): Simple Stairwell Pressurization in a Simple Building + + Question (Part 1): + - Stairwells pressurized with UNTREATED outside air. + - Can the stairwell be pressurized? (Check height limit vs building height) + + Method (OpenFire): + - Eq. 50.17: Stairwell temperature for untreated pressurization air, T_s + - Eq. 50.16: Flow area factor, F_r + - Eq. 50.15: Height limit, H_m + + Decision criterion: + - If H_m >= building height, then the stairwell can be pressurized (under these assumptions). + + Unit policy: + - All calculations performed in SI units (m, m², Pa, °C). + - Inputs provided in Imperial / °F are explicitly converted. + + Given conversions (per user requirement): + - 1 ft = 0.3048 m + - 1 in H2O = 249.09 Pa + """ + + import ofire + + + # ============================================================================= + # 1) Unit conversions (explicit) + # ============================================================================= + FT_TO_M = 0.3048 + IN_H2O_TO_PA = 249.09 + + # Additional necessary conversions (temperature, area) + def f_to_c(temp_f: float) -> float: + """Convert temperature from °F to °C.""" + return (temp_f - 32.0) * (5.0 / 9.0) + + def ft2_to_m2(area_ft2: float) -> float: + """Convert area from ft² to m² using 1 ft = 0.3048 m.""" + return area_ft2 * (FT_TO_M ** 2) + + + # ============================================================================= + # 2) Inputs (as given in the problem statement) + # ============================================================================= + t_outdoor_f = 10.0 + t_building_f = 70.0 + + delta_p_min_in_h2o = 0.10 + delta_p_max_in_h2o = 0.35 + + floor_to_floor_height_ft = 10.0 + building_height_ft = 200.0 + + a_sb_ft2 = 0.34 # flow area between stairwell and building (typical floor) + a_bo_ft2 = 0.30 # flow area per stairwell between building and outside + + eta = 0.15 # heat transfer factor (given / suggested) + + + # ============================================================================= + # 3) Convert inputs to SI (SI-only variables used in calculations) + # ============================================================================= + t_outdoor_c = f_to_c(t_outdoor_f) + t_building_c = f_to_c(t_building_f) + + delta_p_min_pa = delta_p_min_in_h2o * IN_H2O_TO_PA + delta_p_max_pa = delta_p_max_in_h2o * IN_H2O_TO_PA + + floor_to_floor_height_m = floor_to_floor_height_ft * FT_TO_M + building_height_m = building_height_ft * FT_TO_M + + a_sb_m2 = ft2_to_m2(a_sb_ft2) + a_bo_m2 = ft2_to_m2(a_bo_ft2) + + + # ============================================================================= + # 4) Calculation (OpenFire — SFPE HB Ch.50 Eq. 50.17, 50.16, 50.15) + # ============================================================================= + # Eq. 50.17 — stairwell temperature for untreated air + t_stair_c = ofire.sfpe_handbook.chapter_50.equation_50_17.stairwell_temperature( + t_0=t_outdoor_c, + eta=eta, + t_b=t_building_c, + ) + + # Eq. 50.16 — flow area factor + f_r = ofire.sfpe_handbook.chapter_50.equation_50_16.factor( + a_sb=a_sb_m2, + a_bo=a_bo_m2, + t_b=t_building_c, + t_s=t_stair_c, + ) + + # Eq. 50.15 — height limit + h_m = ofire.sfpe_handbook.chapter_50.equation_50_15.height_limit( + f_r=f_r, + delta_p_max=delta_p_max_pa, + delta_p_min=delta_p_min_pa, + t_0=t_outdoor_c, + t_s=t_stair_c, + ) + + can_pressurize = h_m >= building_height_m + + + # ============================================================================= + # 5) Print inputs and results (always) + # ============================================================================= + print("=" * 72) + print("OpenFire — SFPE Handbook Ch.50 — Example 3 (Part 1)") + print("Simple Stairwell Pressurization (Untreated Outside Air)") + print("=" * 72) + + print("\nINPUTS (original, as stated):") + print(f" Outdoor temperature, T_o = {t_outdoor_f:.3f} °F") + print(f" Building temperature, T_b = {t_building_f:.3f} °F") + print(f" Min pressure diff, Δp_min = {delta_p_min_in_h2o:.3f} in H2O") + print(f" Max pressure diff, Δp_max = {delta_p_max_in_h2o:.3f} in H2O") + print(f" Floor-to-floor height = {floor_to_floor_height_ft:.3f} ft") + print(f" Building height = {building_height_ft:.3f} ft") + print(f" Flow area stair→building, A_SB = {a_sb_ft2:.3f} ft²") + print(f" Flow area building→outside, A_BO = {a_bo_ft2:.3f} ft² (per stairwell)") + print(f" Heat transfer factor, η = {eta:.3f}") + + print("\nINPUTS (converted to SI; used in calculation):") + print(f" Outdoor temperature, T_o = {t_outdoor_c:.6f} °C") + print(f" Building temperature, T_b = {t_building_c:.6f} °C") + print(f" Min pressure diff, Δp_min = {delta_p_min_pa:.6f} Pa") + print(f" Max pressure diff, Δp_max = {delta_p_max_pa:.6f} Pa") + print(f" Floor-to-floor height = {floor_to_floor_height_m:.6f} m") + print(f" Building height = {building_height_m:.6f} m") + print(f" Flow area stair→building, A_SB = {a_sb_m2:.8f} m²") + print(f" Flow area building→outside, A_BO = {a_bo_m2:.8f} m²") + + print("\nRESULTS (SI):") + print(f" Stairwell temperature, T_s = {t_stair_c:.6f} °C (Eq. 50.17)") + print(f" Flow area factor, F_r = {f_r:.6f} (-) (Eq. 50.16)") + print(f" Height limit, H_m = {h_m:.6f} m (Eq. 50.15)") + print(f" Decision (H_m >= building height) = {can_pressurize}") + + print("\nCONCLUSION:") + if can_pressurize: + print(" The stairwell CAN be pressurized under these assumptions.") + else: + print(" The stairwell CANNOT be pressurized under these assumptions.") + + print("\nNotes:") + print(" - All calculations performed in SI units (m, m², Pa, °C).") + print(" - OpenFire functions used:") + print(" * Eq. 50.17: ofire.sfpe_handbook.chapter_50.equation_50_17.stairwell_temperature") + print(" * Eq. 50.16: ofire.sfpe_handbook.chapter_50.equation_50_16.factor") + print(" * Eq. 50.15: ofire.sfpe_handbook.chapter_50.equation_50_15.height_limit") + print("=" * 72) Part 2 @@ -116,12 +271,153 @@ Part 2 .. code-block:: python - # Code to be added soon + """ + OpenFire Example — SFPE Handbook Chapter 50 (Smoke Control) + Example 3 (Part 2): Simple Stairwell Pressurization in a Simple Building + Question (Part 2): + - Stairwells pressurized with TREATED air at 70°F (i.e., supply air temperature). + - Can the stairwell be pressurized? (Check height limit vs building height) -Part 3 -^^^^^^ + Method (OpenFire): + - Eq. 50.16: Flow area factor, F_r + - Eq. 50.15: Height limit, H_m -.. code-block:: python + Decision criterion: + - If H_m >= building height, then the stairwell can be pressurized (under these assumptions). + + Unit policy: + - All calculations performed in SI units (m, m², Pa, °C). + - Inputs provided in Imperial / °F are explicitly converted. + + Given conversions (per user requirement): + - 1 ft = 0.3048 m + - 1 in H2O = 249.09 Pa + """ + + import ofire + + + # ============================================================================= + # 1) Unit conversions (explicit) + # ============================================================================= + FT_TO_M = 0.3048 + IN_H2O_TO_PA = 249.09 + + # Additional necessary conversions (temperature, area) + def f_to_c(temp_f: float) -> float: + """Convert temperature from °F to °C.""" + return (temp_f - 32.0) * (5.0 / 9.0) + + def ft2_to_m2(area_ft2: float) -> float: + """Convert area from ft² to m² using 1 ft = 0.3048 m.""" + return area_ft2 * (FT_TO_M ** 2) + + + # ============================================================================= + # 2) Inputs (as given in the problem statement) + # ============================================================================= + t_outdoor_f = 10.0 + t_building_f = 70.0 + + # Part 2 condition: treated stair pressurization air temperature + t_stair_supply_f = 70.0 + + delta_p_min_in_h2o = 0.10 + delta_p_max_in_h2o = 0.35 + + floor_to_floor_height_ft = 10.0 + building_height_ft = 200.0 + + a_sb_ft2 = 0.34 # flow area between stairwell and building (typical floor) + a_bo_ft2 = 0.30 # flow area per stairwell between building and outside - # Code to be added soon \ No newline at end of file + + # ============================================================================= + # 3) Convert inputs to SI (SI-only variables used in calculations) + # ============================================================================= + t_outdoor_c = f_to_c(t_outdoor_f) + t_building_c = f_to_c(t_building_f) + t_stair_c = f_to_c(t_stair_supply_f) + + delta_p_min_pa = delta_p_min_in_h2o * IN_H2O_TO_PA + delta_p_max_pa = delta_p_max_in_h2o * IN_H2O_TO_PA + + floor_to_floor_height_m = floor_to_floor_height_ft * FT_TO_M + building_height_m = building_height_ft * FT_TO_M + + a_sb_m2 = ft2_to_m2(a_sb_ft2) + a_bo_m2 = ft2_to_m2(a_bo_ft2) + + + # ============================================================================= + # 4) Calculation (OpenFire — SFPE HB Ch.50 Eq. 50.16 and 50.15) + # ============================================================================= + # Eq. 50.16 — flow area factor + f_r = ofire.sfpe_handbook.chapter_50.equation_50_16.factor( + a_sb=a_sb_m2, + a_bo=a_bo_m2, + t_b=t_building_c, + t_s=t_stair_c, + ) + + # Eq. 50.15 — height limit + h_m = ofire.sfpe_handbook.chapter_50.equation_50_15.height_limit( + f_r=f_r, + delta_p_max=delta_p_max_pa, + delta_p_min=delta_p_min_pa, + t_0=t_outdoor_c, + t_s=t_stair_c, + ) + + can_pressurize = h_m >= building_height_m + + + # ============================================================================= + # 5) Print inputs and results (always) + # ============================================================================= + print("=" * 72) + print("OpenFire — SFPE Handbook Ch.50 — Example 3 (Part 2)") + print("Simple Stairwell Pressurization (Treated Air at 70°F)") + print("=" * 72) + + print("\nINPUTS (original, as stated):") + print(f" Outdoor temperature, T_o = {t_outdoor_f:.3f} °F") + print(f" Building temperature, T_b = {t_building_f:.3f} °F") + print(f" Treated stair supply temp, T_s = {t_stair_supply_f:.3f} °F") + print(f" Min pressure diff, Δp_min = {delta_p_min_in_h2o:.3f} in H2O") + print(f" Max pressure diff, Δp_max = {delta_p_max_in_h2o:.3f} in H2O") + print(f" Floor-to-floor height = {floor_to_floor_height_ft:.3f} ft") + print(f" Building height = {building_height_ft:.3f} ft") + print(f" Flow area stair→building, A_SB = {a_sb_ft2:.3f} ft²") + print(f" Flow area building→outside, A_BO = {a_bo_ft2:.3f} ft² (per stairwell)") + + print("\nINPUTS (converted to SI; used in calculation):") + print(f" Outdoor temperature, T_o = {t_outdoor_c:.6f} °C") + print(f" Building temperature, T_b = {t_building_c:.6f} °C") + print(f" Stairwell temperature, T_s = {t_stair_c:.6f} °C") + print(f" Min pressure diff, Δp_min = {delta_p_min_pa:.6f} Pa") + print(f" Max pressure diff, Δp_max = {delta_p_max_pa:.6f} Pa") + print(f" Floor-to-floor height = {floor_to_floor_height_m:.6f} m") + print(f" Building height = {building_height_m:.6f} m") + print(f" Flow area stair→building, A_SB = {a_sb_m2:.8f} m²") + print(f" Flow area building→outside, A_BO = {a_bo_m2:.8f} m²") + + print("\nRESULTS (SI):") + print(f" Flow area factor, F_r = {f_r:.6f} (-) (Eq. 50.16)") + print(f" Height limit, H_m = {h_m:.6f} m (Eq. 50.15)") + print(f" Decision (H_m >= building height) = {can_pressurize}") + + print("\nCONCLUSION:") + if can_pressurize: + print(" The stairwell CAN be pressurized under these assumptions.") + else: + print(" The stairwell CANNOT be pressurized under these assumptions.") + + print("\nNotes:") + print(" - All calculations performed in SI units (m, m², Pa, °C).") + print(" - For treated air, T_s is taken as the treated supply temperature.") + print(" - OpenFire functions used:") + print(" * Eq. 50.16: ofire.sfpe_handbook.chapter_50.equation_50_16.factor") + print(" * Eq. 50.15: ofire.sfpe_handbook.chapter_50.equation_50_15.height_limit") + print("=" * 72) \ No newline at end of file From 9dfb852502e75f9aee4167d8a9363740ac1fbbfe Mon Sep 17 00:00:00 2001 From: Simon Santamaria Date: Fri, 16 Jan 2026 20:06:38 +0000 Subject: [PATCH 22/22] Update crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst b/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst index 95f66f2..b2a2faf 100644 --- a/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst +++ b/crates/python_api/docs/examples/sfpe-handbook/chapter-50.rst @@ -100,7 +100,7 @@ Example 2: Door Opening Force print("=" * 72) -Example 3: Simple Stairwell Pressurisation in a Simple Building +Example 3: Simple Stairwell Pressurization in a Simple Building ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Part 1