diff --git a/crates/python_api/docs/api/sfpe-handbook.rst b/crates/python_api/docs/api/sfpe-handbook.rst index 9ed1e63b..b44b787d 100644 --- a/crates/python_api/docs/api/sfpe-handbook.rst +++ b/crates/python_api/docs/api/sfpe-handbook.rst @@ -104,6 +104,29 @@ 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: + +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/docs/guide/equation-relationships.rst b/crates/python_api/docs/guide/equation-relationships.rst index 6bb05c4a..0788a5c8 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 --------------------------------------- diff --git a/crates/python_api/src/sfpe_handbook/chapter_50.rs b/crates/python_api/src/sfpe_handbook/chapter_50.rs index a4a74490..9d0a733c 100644 --- a/crates/python_api/src/sfpe_handbook/chapter_50.rs +++ b/crates/python_api/src/sfpe_handbook/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; @@ -21,5 +24,8 @@ 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))?; + 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_18.rs b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs new file mode 100644 index 00000000..a038d1a8 --- /dev/null +++ b/crates/python_api/src/sfpe_handbook/chapter_50/equation_50_18.rs @@ -0,0 +1,49 @@ +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 evaluation of exposure to smoke. +/// +/// .. math:: +/// +/// FED = \frac{\sum (C_i \Delta t_i)}{LCt_{50}} +/// +/// 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 (g/m³) +/// +/// Args: +/// c_i (list[float]): Concentration values at each time interval (g/m³) +/// delta_t_i (float): Time intervals (s) +/// lc_t50 (float): Lethal exposure dose from test data (g/m³) +/// +/// 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 +/// >>> 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: f64, 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(()) +} 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 00000000..8c06d637 --- /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 {\delta}_m C_i} +/// +/// where: +/// +/// - :math:`S_i` is the visibility through smoke (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): 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) +/// +/// 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 illuminated 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(()) +} 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 00000000..2f2c6ba0 --- /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 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 diff --git a/crates/sfpe_handbook/src/chapter_50.rs b/crates/sfpe_handbook/src/chapter_50.rs index 069e53a0..f7dde780 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 00000000..f4899fdd --- /dev/null +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_18.rs @@ -0,0 +1,27 @@ +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: String, lc_t50: String) -> String { + format!( + "{} = \\frac{{ \\sum {} \\times {} }}{{ {} }}", + fed, c_i, delta_t, lc_t50 + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fed() { + let c_i = vec![0.001, 0.003]; + let delta_t = 1.0; + let lc_t50 = 0.015; + 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 new file mode 100644 index 00000000..142fc6fb --- /dev/null +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_19.rs @@ -0,0 +1,23 @@ +pub fn visibility(k: f64, delta_m: f64, c_i: f64) -> f64 { + k / (2.303 * delta_m * c_i) +} + +#[cfg(not(coverage))] +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, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_visibility() { + let result = visibility(8.0, 0.22, 1.0); + let expected = 15.78968144; + assert!((result - expected).abs() < 1e-6); + } +} 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 00000000..b9ff493e --- /dev/null +++ b/crates/sfpe_handbook/src/chapter_50/equation_50_20.rs @@ -0,0 +1,23 @@ +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, l: String, lambda: String) -> String { + format!( + "{} = \\frac{{{} \\times {}}}{{\\ln(1 - \\frac{{{}}}{{100}})}}", + s_i, k, l, lambda, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_visibility() { + let result = visibility(8.0, 10.0, 95.0); + let expected = 26.70465606; + assert!((result - expected).abs() < 1e-6); + } +}