Source code for enex_analysis.uv_treatment

"""UV water treatment utility functions.

Functions for UV lamp power scheduling, turbidity-based parameter
lookup, and required exposure time calculation (Radial Model).
"""

from __future__ import annotations

import math

from . import calc_util as cu


[docs] def calc_uv_lamp_power( current_time_s: float, period_sec: float, num_switching: int, exposure_sec: float, lamp_watts: float, ) -> float: """Calculate UV lamp power at a given time instant. The lamp switches on ``num_switching`` times per ``period_sec``, each activation lasting ``exposure_sec``. Parameters ---------- current_time_s : float Current simulation time [s]. period_sec : float Switching period (e.g. 3 h → 10800 s). num_switching : int Number of on-cycles per period. exposure_sec : float Duration of each on-cycle [s]. lamp_watts : float Rated lamp power [W]. Returns ------- float Instantaneous lamp power [W] (0 or ``lamp_watts``). """ if num_switching <= 0 or lamp_watts <= 0: return 0.0 time_in_period = current_time_s % period_sec interval = (period_sec - num_switching * exposure_sec) / (num_switching + 1) for i in range(num_switching): start_time = interval * (i + 1) + i * exposure_sec if start_time <= time_in_period < start_time + exposure_sec: return lamp_watts return 0.0
[docs] def get_uv_params_from_turbidity(turbidity_ntu: float) -> dict: """Return UV parameters from a turbidity lookup table. Table data based on *Table 1. Effect of Turbidity on UVT, UV Absorbance, UV Intensity, and Exposure Time*. Parameters ---------- turbidity_ntu : float Turbidity value [NTU]. Returns ------- dict Keys: ``uv_absorbance``, ``uv_transmittance_percent``, ``reference_intensity_mw_cm2``, ``reference_exposure_time_sec``. """ # [Turbidity (NTU), % UVT, A254, Intensity (mW/cm²), Exposure (s)] turbidity_table = [ [0.25, 86, 0.07, 0.40, 12.4], [5.0, 78, 0.11, 0.39, 12.8], [10.0, 71, 0.15, 0.36, 13.9], [20.1, 59, 0.23, 0.33, 15.0], ] turbidity_values = [row[0] for row in turbidity_table] def _row_to_dict(row): return { "uv_absorbance": row[2], "uv_transmittance_percent": row[1], "reference_intensity_mw_cm2": row[3], "reference_exposure_time_sec": row[4], } if turbidity_ntu <= turbidity_values[0]: return _row_to_dict(turbidity_table[0]) if turbidity_ntu >= turbidity_values[-1]: return _row_to_dict(turbidity_table[-1]) # Linear interpolation for i in range(len(turbidity_values) - 1): if turbidity_values[i] <= turbidity_ntu < turbidity_values[i + 1]: t1, t2 = turbidity_values[i], turbidity_values[i + 1] row1, row2 = turbidity_table[i], turbidity_table[i + 1] ratio = (turbidity_ntu - t1) / (t2 - t1) return { "uv_absorbance": row1[2] + ratio * (row2[2] - row1[2]), "uv_transmittance_percent": row1[1] + ratio * (row2[1] - row1[1]), "reference_intensity_mw_cm2": row1[3] + ratio * (row2[3] - row1[3]), "reference_exposure_time_sec": row1[4] + ratio * (row2[4] - row1[4]), } # Exact match fallback for i, t_val in enumerate(turbidity_values): if abs(turbidity_ntu - t_val) < 1e-6: return _row_to_dict(turbidity_table[i]) return _row_to_dict(turbidity_table[0])
[docs] def calc_uv_exposure_time( radius_cm: float, uvc_output_W: float, lamp_arc_length_cm: float, target_dose_mj_cm2: float = 186, turbidity_ntu: float = 0.25, ) -> float: """Calculate required UV lamp exposure time via Radial Model. Reference: ADA453967.pdf — Radial Model for UV disinfection. Parameters ---------- radius_cm : float Tank radius (lamp-to-wall distance) [cm]. uvc_output_W : float UV-C output power of the lamp [W]. lamp_arc_length_cm : float Arc length of the lamp [cm]. target_dose_mj_cm2 : float Target germicidal dose [mJ/cm²]. Default 186 (EPA 4-log virus). turbidity_ntu : float Water turbidity [NTU]. Returns ------- float Required single-exposure time [min]. """ uv_params = get_uv_params_from_turbidity(turbidity_ntu) uv_absorbance = uv_params["uv_absorbance"] absorption_coeff = 2.303 * uv_absorbance # Linear power density [mW/cm] p_l_mw_cm = (uvc_output_W * cu.W2mW) / lamp_arc_length_cm # Intensity at tank wall [mW/cm²] intensity_mw_cm2 = (p_l_mw_cm / (2 * math.pi * radius_cm)) * math.exp(-absorption_coeff * radius_cm) required_time_sec = target_dose_mj_cm2 / intensity_mw_cm2 return required_time_sec / 60