|
| 1 | +use anyhow::{anyhow, Result}; |
| 2 | +use evalexpr::{ |
| 3 | + eval_with_context, Context, ContextWithMutableVariables, DefaultNumericTypes, HashMapContext, |
| 4 | + Value, |
| 5 | +}; |
| 6 | + |
| 7 | +/// Convert voltage to temperature using a configurable mathematical formula |
| 8 | +/// |
| 9 | +/// This function evaluates a mathematical expression where 'voltage' is available as a variable. |
| 10 | +/// The formula should return temperature in Kelvin. |
| 11 | +/// |
| 12 | +/// # Arguments |
| 13 | +/// * `formula` - Mathematical expression as string (e.g., "1.0 / (1.0 / 298.15 + ln(10000.0 * voltage / (5.0 - voltage) / 10000.0) / 3977.0)") |
| 14 | +/// * `voltage` - Input voltage in volts |
| 15 | +/// |
| 16 | +/// # Returns |
| 17 | +/// * `Result<f64>` - Temperature in Kelvin, or error if formula evaluation fails |
| 18 | +/// |
| 19 | +/// # Example |
| 20 | +/// ```rust |
| 21 | +/// use rust_photoacoustic::utility::temperature_conversion::convert_voltage_to_temperature; |
| 22 | +/// |
| 23 | +/// // NTC formula for 10kΩ NTC with β=3977, 10kΩ voltage divider, 5V supply |
| 24 | +/// let formula = "1.0 / (1.0 / 298.15 + math::ln(10000.0 * voltage / (5.0 - voltage) / 10000.0) / 3977.0)".to_string(); |
| 25 | +/// let temp_k = convert_voltage_to_temperature(formula, 2.5).unwrap(); |
| 26 | +/// assert!((temp_k - 298.15).abs() < 1.0); // Should be close to 25°C (298.15K) |
| 27 | +/// ``` |
| 28 | +pub fn convert_voltage_to_temperature(formula: String, voltage: f32) -> Result<f64> { |
| 29 | + // Validate input voltage |
| 30 | + if voltage < 0.0 || voltage > 10.0 { |
| 31 | + return Err(anyhow!( |
| 32 | + "Invalid voltage: {:.3}V (must be between 0V and 10V)", |
| 33 | + voltage |
| 34 | + )); |
| 35 | + } |
| 36 | + |
| 37 | + // Validate formula contains 'voltage' variable |
| 38 | + if !formula.contains("voltage") { |
| 39 | + return Err(anyhow!( |
| 40 | + "Formula must contain 'voltage' variable, got: '{}'", |
| 41 | + formula |
| 42 | + )); |
| 43 | + } |
| 44 | + |
| 45 | + // Create evaluation context with voltage variable |
| 46 | + let mut context = HashMapContext::<DefaultNumericTypes>::new(); |
| 47 | + context.set_builtin_functions_disabled(false).unwrap(); |
| 48 | + context.set_value("voltage".into(), Value::Float(voltage as f64))?; |
| 49 | + |
| 50 | + // Evaluate the formula |
| 51 | + let result = eval_with_context(&formula, &context).map_err(|e| { |
| 52 | + anyhow!( |
| 53 | + "Failed to evaluate temperature formula '{}': {}", |
| 54 | + formula, |
| 55 | + e |
| 56 | + ) |
| 57 | + })?; |
| 58 | + |
| 59 | + // Convert result to f64 |
| 60 | + let temperature_k = result.as_float().or_else(|_| { |
| 61 | + Err(anyhow!( |
| 62 | + "Formula did not return a numeric value: '{}'", |
| 63 | + formula |
| 64 | + )) |
| 65 | + })?; |
| 66 | + |
| 67 | + // Validate result (reasonable temperature range in Kelvin: -50°C to 100°C) |
| 68 | + if temperature_k < 223.15 || temperature_k > 373.15 { |
| 69 | + return Err(anyhow!( |
| 70 | + "Calculated temperature {:.2}K ({:.2}°C) is outside reasonable range (-50°C to 100°C)", |
| 71 | + temperature_k, |
| 72 | + temperature_k - 273.15 |
| 73 | + )); |
| 74 | + } |
| 75 | + |
| 76 | + Ok(temperature_k) |
| 77 | +} |
| 78 | + |
| 79 | +#[cfg(test)] |
| 80 | +mod tests { |
| 81 | + use super::*; |
| 82 | + use approx::assert_relative_eq; |
| 83 | + |
| 84 | + #[test] |
| 85 | + fn test_convert_voltage_to_temperature_ntc_formula() { |
| 86 | + // Formula for 10kΩ NTC with β=3977, 10kΩ voltage divider, 5V supply |
| 87 | + let formula = |
| 88 | + "1.0 / (1.0 / 298.15 + math::ln(10000.0 * voltage / (5.0 - voltage) / 10000.0) / 3977.0)" |
| 89 | + .to_string(); |
| 90 | + |
| 91 | + // Test at 2.5V (should be close to 25°C = 298.15K for balanced voltage divider) |
| 92 | + let temp_k = convert_voltage_to_temperature(formula.clone(), 2.5).unwrap(); |
| 93 | + assert_relative_eq!(temp_k, 298.15, epsilon = 1.0); |
| 94 | + |
| 95 | + // Test at other voltages |
| 96 | + let temp_k_low = convert_voltage_to_temperature(formula.clone(), 1.0).unwrap(); |
| 97 | + let temp_k_high = convert_voltage_to_temperature(formula.clone(), 4.0).unwrap(); |
| 98 | + |
| 99 | + // Lower voltage should mean higher temperature (NTC characteristic) |
| 100 | + assert!(temp_k_low > temp_k_high); |
| 101 | + } |
| 102 | + |
| 103 | + #[test] |
| 104 | + fn test_convert_voltage_to_temperature_simple_linear() { |
| 105 | + // Simple linear formula: temperature = 273.15 + voltage * 10 (10°C per volt) |
| 106 | + let formula = "273.15 + voltage * 10.0".to_string(); |
| 107 | + |
| 108 | + let temp_k = convert_voltage_to_temperature(formula, 2.5).unwrap(); |
| 109 | + assert_relative_eq!(temp_k, 298.15, epsilon = 0.001); // 273.15 + 2.5 * 10 = 298.15K |
| 110 | + } |
| 111 | + |
| 112 | + #[test] |
| 113 | + fn test_convert_voltage_to_temperature_invalid_voltage() { |
| 114 | + let formula = "273.15 + voltage * 10.0".to_string(); |
| 115 | + |
| 116 | + // Test negative voltage |
| 117 | + assert!(convert_voltage_to_temperature(formula.clone(), -1.0).is_err()); |
| 118 | + |
| 119 | + // Test excessive voltage |
| 120 | + assert!(convert_voltage_to_temperature(formula, 15.0).is_err()); |
| 121 | + } |
| 122 | + |
| 123 | + #[test] |
| 124 | + fn test_convert_voltage_to_temperature_invalid_formula() { |
| 125 | + // Test malformed formula |
| 126 | + assert!(convert_voltage_to_temperature("invalid formula".to_string(), 2.5).is_err()); |
| 127 | + |
| 128 | + // Test formula with undefined variable |
| 129 | + assert!( |
| 130 | + convert_voltage_to_temperature("273.15 + unknown_var * 10.0".to_string(), 2.5).is_err() |
| 131 | + ); |
| 132 | + } |
| 133 | + |
| 134 | + #[test] |
| 135 | + fn test_convert_voltage_to_temperature_unreasonable_result() { |
| 136 | + // Formula that would produce unreasonable temperature |
| 137 | + let formula = "1000.0".to_string(); // 1000K = 726.85°C (too hot) |
| 138 | + assert!(convert_voltage_to_temperature(formula, 2.5).is_err()); |
| 139 | + |
| 140 | + let formula_cold = "100.0".to_string(); // 100K = -173.15°C (too cold) |
| 141 | + assert!(convert_voltage_to_temperature(formula_cold, 2.5).is_err()); |
| 142 | + } |
| 143 | + |
| 144 | + #[test] |
| 145 | + fn test_convert_voltage_to_temperature_non_numeric_result() { |
| 146 | + // Formula that returns string (should fail) |
| 147 | + let formula = "\"not a number\"".to_string(); |
| 148 | + assert!(convert_voltage_to_temperature(formula, 2.5).is_err()); |
| 149 | + } |
| 150 | +} |
0 commit comments