diff --git a/Manuals/CFAST_Tech_Ref/CFAST_Tech_Ref.tex b/Manuals/CFAST_Tech_Ref/CFAST_Tech_Ref.tex index 6a8b7fd48..53aee40e3 100644 --- a/Manuals/CFAST_Tech_Ref/CFAST_Tech_Ref.tex +++ b/Manuals/CFAST_Tech_Ref/CFAST_Tech_Ref.tex @@ -922,6 +922,7 @@ \section{Sprinkler and Heat Detector Activation} \section{Optical Density} \label{Optical_Density} +\label{Visibility} The optical density calculation depends solely on the soot concentration. Soot production is specified by the user in terms of a yield. Soot is transported like all other gas species and its concentration is used in the optical density calculation. The optical density per unit distance, $D_u$, is given by the expression: \be @@ -946,33 +947,14 @@ \section{Smoke Detection} \section{Fire Suppression} \label{sec:suppression} -Fire suppression by water is predicted using a simple empirical model developed by Madrzykowski \cite{Madrzykowski:1992} and Evans~\cite{Evans:1993}. After activation of the sprinkler, $t > t_{\rm act}$, the heat release rate is assumed to decrease exponentially: +Fire suppression by water is predicted using a simple empirical model developed by Madrzykowski \cite{Madrzykowski:1992} and Evans~\cite{Evans:1993}. After activation of the sprinkler, $t > t_{\rm act}$, the pyrolysis rate and the corresponding heat release rate are assumed to decrease exponentially: \be - \dQ(t) = \dQ(t_{\rm act}) \; {\rm e}^{-(t-t_{\rm act}) /\tau} \quad ; \quad \tau = 3 \, u_{\rm w}^{-1.8} + \dQ(t) = \dQ(t_{\rm act}) \; {\rm e}^{-(t-t_{\rm act}) /\tau} \quad ; \quad \tau = \tau_0 \, \left( \frac{u_{\rm w,0}}{u_{\rm w}} \right)^{1.8} \ee -where $u_{\rm w}$ is the water spray density, expressed in units\footnote{Note the CFAST graphical interface might use other units, but the program does convert the user-specified value into \unit{mm/s}.} of \unit{mm/s}. The product species mass production rates are reduced by the same amount as the heat release rate. +where $\tau$ is referred to as the decay time constant, typically expressed in seconds, and $u_{\rm w}$ is the water spray density, expressed in units of volume per unit area per unit time, such as \unit{L/(min \, m^2)}, \unit{gal/(min \, ft^2)}, or the equivalent velocity\footnote{For consistency with past versions of CFAST, the specified \ct{SPRAY_DENSITY} is expressed in units of m/s in the text-based input file.} in units of mm/s or m/s. The parameters $\tau_0$ and $u_{\rm w,0}$ are emprically derived from a full-scale sprinkler suppression experiment in which the decay time constant is inferred for a specific object or commodity class subjected to a specific spray density. Past versions of CFAST pegged the value of $\tau_0 \, u_{\rm w,0}^{1.8}$ to 3 where $\tau_0$ was expressed in seconds and $u_{\rm w,0}$ in mm/s. The more general approach retains the basic concept of Madrzykowski and Evans, but allows the end user to specify different suppression parameters for each fire. There are assumptions and limitations in this approach. Its main deficiency is that it assumes that sufficient water is applied to the fire to cause a decrease in the rate of heat release. This suppression model cannot handle the case when the fire overwhelms the sprinkler. The suppression model as implemented does not include the effect of a second sprinkler. Detection of all sprinklers is noted, but their activation does not make the fire go out any faster. Further, multiple fires in a room imply multiple ceiling jets. It is not clear how the two ceiling jets should interact. When there is more than one fire, the detection algorithm uses the fire that results in the highest ceiling jet temperature in order to calculate the sprinkler link temperature. -An alternative empirical model is selected for an individual fire by specifying \ct{E_COEFFICIENT} on its \ct{FIRE} namelist. This option replaces the quenching-time correlation for that fire. Let $\chi$ denote \ct{E_COEFFICIENT}, in \unit{m^2/(kg.s)}, and let $u_{\rm w}$ now denote the input spray density in \unit{m/s}. The accumulated delivered water mass per unit area is approximated by -\begin{equation} - m_{\rm w}''(t)=\rho_{\rm w}\int_{t_{\rm act}}^t u_{\rm w}(s)\,\mathrm{d}s, - \qquad \rho_{\rm w}=1000\ \unit{kg/m^3}. -\end{equation} -The suppression rate is $k(t)=\chi m_{\rm w}''(t)$, with units of \unit{1/s}. The prescribed heat release and fuel mass-loss rates are multiplied by the same attenuation factor: -\begin{equation} - S(t)=\exp\left[-\int_{t_{\rm act}}^t k(s)\,\mathrm{d}s\right], - \qquad \dQ(t)=\dQ_0(t)S(t), - \qquad \dot m_{\rm f}(t)=\dot m_{{\rm f},0}(t)S(t). -\end{equation} -Here the subscript 0 denotes the prescribed value at the current time relative to fire ignition. The baseline is not frozen at sprinkler activation. For the constant spray density used by CFAST, with $\Delta t=\max(0,t-t_{\rm act})$, the integrals give -\begin{equation} - S(t)=\exp\left[-\frac{1}{2}\chi\rho_{\rm w}u_{\rm w}(\Delta t)^2\right]. - \label{eq:e_coefficient} -\end{equation} -The exponent is dimensionless. CFAST evaluates this expression analytically, so the water exposure does not depend on the number of solver residual evaluations. Subsequent combustion and oxygen-limitation calculations still apply. - -This formulation follows the FDS empirical burning-rate reduction model, using accumulated delivered water in place of FDS's calculated water mass on the fuel surface. The approximation assumes continuous delivery after activation and neglects evaporation, runoff, and spatial variation of the spray. Wetting starts at sprinkler activation, including for a fire that ignites later. The same first activated sprinkler in the compartment supplies the spray density for all fires there; each fire can have its own $\chi$. Additional sprinklers are not summed. Omitting \ct{E_COEFFICIENT} retains the original model, explicitly setting it to zero disables sprinkler attenuation for that fire, and negative values are invalid. The coefficient must be obtained experimentally for the fuel and application. \section{Tenability} \label{sec:Tenability} diff --git a/Manuals/CFAST_Users_Guide/Appendix_CFAST_Keywords.tex b/Manuals/CFAST_Users_Guide/Appendix_CFAST_Keywords.tex index 5b1902278..3f3e892b0 100644 --- a/Manuals/CFAST_Users_Guide/Appendix_CFAST_Keywords.tex +++ b/Manuals/CFAST_Users_Guide/Appendix_CFAST_Keywords.tex @@ -199,7 +199,7 @@ \section{\texorpdfstring{{\tt DEVC}}{DEVC} (Device Parameters)} \ct{SETPOINT}\footnote{The default is 23.93~\%/m for smoke detectors and 57.22~\degc for heat detectors and sprinklers.} & Real & Sec.~\ref{info:DEVC2} & \degc or \%/m & See note \\ \hline \ct{SETPOINTS} & Real Doublet & Sec.~\ref{info:DEVC2} & \%/m & \\ \hline -\ct{SPRAY_DENSITY} & Real & Sec.~\ref{info:DEVC2} & m/s & \\ \hline +\ct{SPRAY_DENSITY} & Real & Sec.~\ref{info:DEVC2} & m/s & $6.316 \times 10^{-5}$ \\ \hline \ct{SURFACE_ORIENTATION} & Character & Sec.~\ref{chapter:targets} & & \\ \hline \ct{SURFACE_TEMPERATURE} & Real & Sec.~\ref{chapter:targets} & & \\ \hline \ct{TEMPERATURE_DEPTH} & Real & Sec.~\ref{chapter:targets} & & 0.5 \\ \hline @@ -262,15 +262,16 @@ \section{\texorpdfstring{{\tt FIRE}}{FIRE} (Fire Parameters)} \hline \hline \endhead {\bf Parameter} & {\bf Type} & {\bf Reference} & {\bf Units} & {\bf Default} \\ \hline -\ct{COMP_ID} & Character & Sec.~\ref{info:FIRE} & & \\ \hline -\ct{DEVC_ID} & Character & Sec.~\ref{info:FIRE} & & \\ \hline -\ct{E_COEFFICIENT} & Real & Sec.~\ref{info:FIRE} & \unit{m^2/(kg.s)} & Omitted \\ \hline -\ct{FIRE_ID} & Character & Sec.~\ref{info:FIRE} & & \\ \hline -\ct{FYI} & Character & Sec.~\ref{info:FIRE} & & \\ \hline -\ct{ID} & Character & Sec.~\ref{info:FIRE} & & \\ \hline -\ct{IGNITION_CRITERION} & Character & Sec.~\ref{info:FIRE} & & \ct{'TIME'} \\ \hline -\ct{LOCATION} & Real Pair & Sec.~\ref{info:FIRE} & m & \\ \hline -\ct{SETPOINT} & Real & Sec.~\ref{info:FIRE} & s $\mid$ $^\circ$C $\mid$ kW/m$^2$ & 0 s \\ \hline +\ct{COMP_ID} & Character & Sec.~\ref{info:FIRE} & & \\ \hline +\ct{DEVC_ID} & Character & Sec.~\ref{info:FIRE} & & \\ \hline +\ct{FIRE_ID} & Character & Sec.~\ref{info:FIRE} & & \\ \hline +\ct{FYI} & Character & Sec.~\ref{info:FIRE} & & \\ \hline +\ct{ID} & Character & Sec.~\ref{info:FIRE} & & \\ \hline +\ct{IGNITION_CRITERION} & Character & Sec.~\ref{info:FIRE} & & \ct{'TIME'} \\ \hline +\ct{LOCATION} & Real Pair & Sec.~\ref{info:FIRE} & m & \\ \hline +\ct{SETPOINT} & Real & Sec.~\ref{info:FIRE} & s $\mid$ $^\circ$C $\mid$ kW/m$^2$ & 0 s \\ \hline +\ct{SPRAY_DENSITY} & Real & Sec.~\ref{info:FIRE} & m/s & $6.316 \times 10^{-5}$ \\ \hline +\ct{DECAY_TIME} & Real & Sec.~\ref{info:FIRE} & s & 432.8 \\ \hline \end{xltabular} diff --git a/Manuals/CFAST_Users_Guide/Input_File_Chapter.tex b/Manuals/CFAST_Users_Guide/Input_File_Chapter.tex index 524edc253..051e951e6 100644 --- a/Manuals/CFAST_Users_Guide/Input_File_Chapter.tex +++ b/Manuals/CFAST_Users_Guide/Input_File_Chapter.tex @@ -433,14 +433,19 @@ \section{Adding Fires} \item[Set Point] (namelist: \ct{SETPOINT}) The critical value of temperature or heat flux at which ignition will occur. \item[Ignition Target] (namelist: \ct{DEVC_ID}) User-specified target used to calculate surface temperature or incident heat flux to ignite the fire. Target is typically placed at the base of the fire to be ignited. \item[Referenced Fire Properties ID] (namelist: \ct{FIRE_ID}) The name of the associated fire definition for this fire instance can be selected from a list of fire definitions. -\item[E Coefficient] (optional; units: \unit{m^2/(kg.s)}; namelist: \ct{E_COEFFICIENT}) Empirical water suppression coefficient for this fire instance. Leave the field blank to use the original sprinkler quenching-time correlation. A positive value selects the accumulated-water model described below. Zero disables sprinkler attenuation for this fire. Negative values are invalid. Fires referencing the same fire properties may use different coefficients. +\item[Suppression Parameters] Two parameters are used to characterize the suppression of the fire. The first is the \textbf{Decay Time Constant} (default: 432.8~s; namelist: \ct{DECAY_TIME}), and the second is the \textbf{Spray Density} (default: $6.316 \times 10^{-5}$~m/s; namelist: \ct{SPRAY_DENSITY}) These are empirical parameters obtained from a full-scale sprinkler suppression experiment that are appropriate for the given burning object or commodity class. The default values are chosen to be consistent with previous versions of CFAST for which the suppression characteristics were the same for every fire. \end{description} +Fire suppression by water is predicted using a simple empirical model developed by Madrzykowski \cite{Madrzykowski:1992} and Evans~\cite{Evans:1993}. After activation of the sprinkler, $t > t_{\rm act}$, the pyrolysis rate and the corresponding heat release rate are assumed to decrease exponentially: +\be + \dQ(t) = \dQ(t_{\rm act}) \; {\rm e}^{-(t-t_{\rm act}) /\tau} \quad ; \quad \tau = \tau_0 \, \left( \frac{u_{\rm w,0}}{u_{\rm w}} \right)^{1.8} +\ee +where $\tau$ is referred to as the decay time constant, typically expressed in seconds, and $u_{\rm w}$ is the water spray density, expressed in units of volume per unit area per unit time, such as \unit{L/(min \, m^2)}, \unit{gal/(min \, ft^2)}, or the equivalent velocity\footnote{For consistency with past versions of CFAST, the specified \ct{SPRAY_DENSITY} is expressed in units of m/s in the text-based input file.} in units of mm/s or m/s. The parameters $\tau_0$ and $u_{\rm w,0}$ are emprically derived from a full-scale sprinkler suppression experiment in which the decay time constant is inferred for a specific object or commodity class subjected to a specific spray density. Past versions of CFAST pegged the value of $\tau_0 \, u_{\rm w,0}^{1.8}$ to 3 where $\tau_0$ was expressed in seconds and $u_{\rm w,0}$ in mm/s. The more general approach retains the basic concept of Madrzykowski and Evans, but allows the end user to specify different suppression parameters for each fire. Note the following caveats for sprinkler suppression: +\begin{enumerate} +\item The first sprinkler activated controls the heat release rate of the fire. Subsequent sprinkler activations have no additional effect. +\item The water spray has no effect on the gas temperature or secondary sprinkler activation times. +\item A sprinkler can only reduce the heat release rate of the fire; thus, the fire cannot overwhelm an under-designed sprinkler system. +\end{enumerate} -With \ct{E_COEFFICIENT} specified, the current prescribed HRR and fuel mass-loss rate are multiplied by -\begin{equation} - S(t)=\exp\left[-\frac{1}{2}\chi\rho_{\rm w}u_{\rm w}\max(0,t-t_{\rm act})^2\right], -\end{equation} -where $\chi$ is \ct{E_COEFFICIENT}, $\rho_{\rm w}=1000$~\unit{kg/m^3}, $u_{\rm w}$ is the sprinkler's \ct{SPRAY_DENSITY} in \unit{m/s}, and $t_{\rm act}$ is its activation time. This model assumes the delivered water accumulates continuously without evaporation or runoff. The first activated sprinkler in the fire's compartment controls the water delivery. A fire that ignites later is subject to the water exposure accumulated since sprinkler activation. The coefficient must be determined experimentally. For example, adding \ct{E_COEFFICIENT=1.0} to a \ct{FIRE} line selects this model for that instance while leaving the \ct{CHEM} and \ct{TABL} definitions shared with other fires. \section{Fuel Properties} @@ -559,27 +564,10 @@ \section{Sprinklers and Detectors} \item[Depth (Y)] (units: \unit{m}; namelist: \ct{LOCATION(2)}) Distance from the front wall of the compartment. \item[Height (Z)] (units: \unit{m}; namelist: \ct{LOCATION(3)}) Distance from the floor of the compartment. \item[RTI] (default: 50~\unit{(m.s)\tothe{1/2}}; namelist: \ct{RTI}) Response Time Index for the sprinkler or detection device. -\item[Spray Density] (default: $6.316\times 10^{-5}$~\unit{m/s}; namelist: \ct{SPRAY_DENSITY}) Rate of water dispersed by a sprinkler, derived by dividing its volumetric flow rate by its area of application. Suppression is modeled using the experimental correlation developed by Evans and Madrzykowski~\cite{Evans:1993} in which the heat release rate is assumed to decrease exponentially following the activation of a sprinkler at time $t=t_{\rm act}$: - \be - \dQ(t) = \dQ(t_{\rm act}) \; {\rm e}^{-(t-t_{\rm act}) /\tau} \quad ; \quad \tau = 3 \, u_{\rm w}^{-1.8} \label{supp} - \ee - In Eq.~(\ref{supp}), the term $u_{\rm w}$ has units of mm/s, which is obtained by multiplying the specified \ct{SPRAY_DENSITY} in units of m/s by 1000. The default value of $6.316\times 10^{-5}$~\unit{m/s} has been chosen based on the assumption that the spray will decrease the heat release of the fire by 50~\% after 300~s (5~min) following activation. Alternative values can be computed using the following formula: - \be - u_{\rm w} = \left( \frac{ 3 \, \ln(2) }{\Delta t_{1/2}} \right)^{1/1.8} - \ee - where $\Delta t_{1/2}$ is the time in seconds required to decrease the HRR by 50~\%. The value of \ct{SPRAY_DENSITY} should be $u_{\rm w}/1000$ to recover units of m/s. +\item[Spray Density] (default: $6.316\times 10^{-5}$~\unit{m/s}; namelist: \ct{SPRAY_DENSITY}) Rate of water dispersed by a sprinkler, derived by dividing its volumetric flow rate by its area of application. Fire suppression is discussed in Section~\ref{info:FIRE}. \item[FYI] Optional descriptive text retained with the device but not used in the calculation. \end{description} -Smoke detectors activate based on the average smoke concentration of the upper layer of the compartment, not the ceiling jet. Often, the activation of smoke alarms is simulated with a temperature-based criterion (in CFAST as a heat alarm), typically in the range of 5~\unit{\degreeCelsius} to 10~\unit{\degreeCelsius} above ambient. Davis and Notarianni studied the activation of heat and smoke alarms in small and large compartments~\cite{Davis:1996}. They conclude that a temperature rise of approximately 5~\unit{\degreeCelsius} corresponds to activation of ionization alarms for a range of fire sources and ceiling heights. The Nuclear Regulatory Commission includes a default value of 10~\unit{\degreeCelsius} with an RTI value of 5~\unit{(m.s)\tothe{1/2}} in NUREG-1805~\cite{NRCNUREG1805}. \\ - -\noindent Note the following on sprinkler suppression: -\begin{enumerate} -\item The first sprinkler activated controls the heat release rate of the fire. Subsequent sprinkler activations have no additional effect. -\item The water spray has no effect on the gas temperature or secondary sprinkler activation times. -\item A sprinkler can only reduce the heat release rate of the fire; thus, the fire cannot overwhelm an under-designed sprinkler system. -\end{enumerate} - - +Smoke detectors activate based on the average smoke concentration of the upper layer of the compartment, not the ceiling jet. Often, the activation of smoke alarms is simulated with a temperature-based criterion (in CFAST as a heat alarm), typically in the range of 5~\unit{\degreeCelsius} to 10~\unit{\degreeCelsius} above ambient. Davis and Notarianni studied the activation of heat and smoke alarms in small and large compartments~\cite{Davis:1996}. They conclude that a temperature rise of approximately 5~\unit{\degreeCelsius} corresponds to activation of ionization alarms for a range of fire sources and ceiling heights. The Nuclear Regulatory Commission includes a default value of 10~\unit{\degreeCelsius} with an RTI value of 5~\unit{(m.s)\tothe{1/2}} in NUREG-1805~\cite{NRCNUREG1805}. \chapter{Visualization} diff --git a/Manuals/CFAST_Validation_Guide/SCRIPT_FIGURES/Scatterplots/validation_scatterplot_output_baseline.csv b/Manuals/CFAST_Validation_Guide/SCRIPT_FIGURES/Scatterplots/validation_scatterplot_output_baseline.csv index 887279ca0..054b25aff 100644 --- a/Manuals/CFAST_Validation_Guide/SCRIPT_FIGURES/Scatterplots/validation_scatterplot_output_baseline.csv +++ b/Manuals/CFAST_Validation_Guide/SCRIPT_FIGURES/Scatterplots/validation_scatterplot_output_baseline.csv @@ -1,7 +1,7 @@ Quantity,Number of Datasets,Number of Points,Sigma_Experiment,Sigma_Model,Bias HGL Temperature: Forced Ventilation,8,132,0.07,0.21,1.12 HGL Temperature: Natural Ventilation,13,215,0.07,0.35,1.02 -HGL Temperature: No Ventilation,3,24,0.07,0.15,0.97 +HGL Temperature: No Ventilation,3,24,0.07,0.17,0.98 HGL Depth: Open Compartments,14,228,0.05,0.19,0.95 HGL Depth: Closed Compartments,4,34,0.05,0.25,1.45 Ceiling Jet Temperature,13,497,0.07,0.42,1.06 @@ -10,7 +10,7 @@ Oxygen Concentration,5,54,0.08,0.30,0.96 Carbon Dioxide Concentration,5,54,0.08,0.26,0.88 Carbon Monoxide Concentration,2,28,0.19,0.64,1.03 Smoke Concentration,1,14,0.19,0.65,2.92 -Compartment Over-Pressure,4,62,0.23,0.41,1.24 +Compartment Over-Pressure,4,62,0.23,0.42,1.26 Target Temperature,4,423,0.07,0.49,1.26 Surface Temperature,6,395,0.07,0.21,0.99 Target Heat Flux,3,241,0.11,0.57,0.86 diff --git a/Manuals/CFAST_Validation_Guide/Verification_Chapter.tex b/Manuals/CFAST_Validation_Guide/Verification_Chapter.tex index 85d17f488..20d04989b 100644 --- a/Manuals/CFAST_Validation_Guide/Verification_Chapter.tex +++ b/Manuals/CFAST_Validation_Guide/Verification_Chapter.tex @@ -576,9 +576,9 @@ \subsection{Utilizing a Sprinkler to Detect and Suppress a Fire} \end{equation} where $\TL$ and $\Tg$ are the link and gas temperatures, $v$ is the gas speed, and RTI (Response Time Index) is a measure of the sensor's thermal inertia. After the sprinkler link temperature reaches the activation temperature, $t > t_{\rm act}$, the sprinkler will begin to suppress the fire by diminishing its heat release rate at the exponential rate described by \cite{Evans:1993}: \begin{equation} -\dQ(t) = \dQ(t_{\rm act}) \; {\rm e}^{-(t-t_{\rm act}) /\tau} \quad ; \quad \tau = 3 \, u_{\rm w}^{-1.8} +\dQ(t) = \dQ(t_{\rm act}) \; {\rm e}^{-(t-t_{\rm act}) /\tau} \quad ; \quad \tau = \tau_0 \, \left( \frac{ u_{\rm w,0} }{u_{\rm w} } \right)^{1.8} \quad ; \quad \tau_0 \, u_{\rm w,0}^{1.8} = 3 \end{equation} -where $u_{\rm w}$ is the water spray density, expressed in units of mm/s. Figure~\ref{sprinkler1} shows the sprinkler link temperature rising as the fire burns in the compartment and the heat release rate decreasing exponentially once the sprinkler has been activated. +where $u_{\rm w}$ is the sprinkler spray density, expressed in units of mm/s. Figure~\ref{sprinkler1} shows the sprinkler link temperature rising as the fire burns in the compartment and the heat release rate decreasing exponentially once the sprinkler has been activated. \begin{figure}[!ht] \begin{tabular*}{\textwidth}{l@{\extracolsep{\fill}}r} @@ -589,31 +589,28 @@ \subsection{Utilizing a Sprinkler to Detect and Suppress a Fire} \label{sprinkler1} \end{figure} -\subsection{Suppression by Accumulated Water} -\label{verify:e_coefficient} +\subsection{Suppression of Fires with Individual Decay Times} +\label{verify:suppression} -The case \ct{Sprinkler/e_coefficient.in} tests fire suppression by continuously accumulating sprinkler water. Each constant methane burner releases 36~kW over an area of 0.36~\unit{m^2}, corresponding to a 0.6~m square at 100~\unit{kW/m^2}. +The case \ct{Sprinkler/suppression.in} tests sprinkler suppression with a separate decay time constant for each of eight 36~kW fires occupying three 10~m by 10~m by 4~m vented compartments. Four fires in Room A share the same chemistry and HRR time history but use specified \ct{DECAY_TIME} values of 30~s, 15~s, and 1000~s, or omit the parameter to use the default 432.8~s. Room A's sprinkler has a spray density of $10^{-4}$~\unit{m/s}, an RTI of 20~\unit{(m.s)\tothe{1/2}}, and a 30~\unit{\degreeCelsius} activation temperature. Room~B uses twice the sprinkler spray density and an RTI of 10~\unit{(m.s)\tothe{1/2}} with the same activation temperature. It contains a constant fire with a decay time of 30~s, a fire growing from 18~kW to 54~kW over 60~s with a decay time of 60~s, and a constant fire with a decay time of 300~s that ignites 15~s after sprinkler activation. Room C contains a constant fire with a decay time of 30~s but no sprinkler. -Eight fires occupy three 10~m by 10~m by 4~m compartments, each vented to the outdoors. Four fires in Room A share the same chemistry and HRR table but use \ct{E_COEFFICIENT} values of 1, 2, and 0~\unit{m^2/(kg.s)}, or omit the parameter to select the original quenching-time model. Room A's sprinkler has a spray density of $10^{-4}$~\unit{m/s}, an RTI of 20~\unit{(m.s)\tothe{1/2}}, and a 30~\unit{\degreeCelsius} set point. Room B uses twice the spray density and an RTI of 10~\unit{(m.s)\tothe{1/2}} with the same set point. It contains a constant fire with coefficient 1, a fire growing from 18 to 54~kW over 60~s with coefficient 0.5, and a constant fire with coefficient 0.1 that ignites at 15~s, after sprinkler activation. Room C contains a constant fire with coefficient 1 and no sprinkler. - -For each fire using the new model, the expected rate is +For an ignited fire after sprinkler activation, the expected HRR is \begin{equation} - \dQ(t)=\dQ_0(t)\exp\left[-\frac{1}{2}\chi\rho_{\rm w}u_{\rm w}\max(0,t-t_{\rm act})^2\right], - \qquad \rho_{\rm w}=1000\ \unit{kg/m^3}. + \dQ(t) = \dQ_0 (t_{\rm act}) \, \exp\left(-\frac{t-t_{\rm act}}{\tau}\right) \qquad \tau=\tau_0 \, \left(\frac{u_{\rm w,0}}{u_{\rm w}}\right)^{1.8} \end{equation} -The growing fire tests evaluation of the current prescribed HRR, and the delayed fire tests water exposure before ignition. The explicit-zero and dry-room fires retain their prescribed HRR. The legacy fire follows $\dQ(t)=36\exp[-\max(0,t-t_{\rm act})/\tau]$~kW, with $\tau=3/(0.1)^{1.8}$~s. The existing startup filter for adiabatic compartments ramps rates over the first second. +Here $\tau_0$ is the fire's \ct{DECAY_TIME}, $u_{\rm w,0}$ is its reference \ct{SPRAY_DENSITY}, and $u_{\rm w}$ is the activated sprinkler's \ct{SPRAY_DENSITY}. All fires use the default reference spray density, approximately $6.316\times10^{-5}$~\unit{m/s}. The growing fire tests decay from the HRR at sprinkler activation. The delayed fire tests ignition after sprinkler activation, with the constant prescribed HRR extended to the activation time. The 1000~s fire decays slowly; only the dry-room fire retains its prescribed HRR throughout the simulation. The default-parameter fire reproduces the original quenching-time correlation to the precision of the defaults. The existing startup filter for adiabatic compartments ramps rates over the first second. -The simulation runs for 60~s. Figure~\ref{fig:e_coefficient} compares the calculated and analytical HRR histories for all eight fires, using each compartment's calculated sprinkler activation time. The expected fuel mass-loss rate is the HRR divided by the heat of combustion, 50~MJ/kg. +The simulation runs for 60~s. Figure~\ref{fig:suppression} compares the calculated and analytical HRR histories for all eight fires, using each compartment's calculated sprinkler activation time. \begin{figure}[!ht] \begin{tabular*}{\textwidth}{l@{\extracolsep{\fill}}r} -\includegraphics[width=3.0in]{SCRIPT_FIGURES/Verification/e_coefficient_room_a} & -\includegraphics[width=3.0in]{SCRIPT_FIGURES/Verification/e_coefficient_room_b} \\ -\includegraphics[width=3.0in]{SCRIPT_FIGURES/Verification/e_coefficient_controls} & -\includegraphics[width=3.0in]{SCRIPT_FIGURES/Verification/e_coefficient_delayed} +\includegraphics[width=3.0in]{SCRIPT_FIGURES/Verification/suppression_room_a} & +\includegraphics[width=3.0in]{SCRIPT_FIGURES/Verification/suppression_room_b} \\ +\includegraphics[width=3.0in]{SCRIPT_FIGURES/Verification/suppression_controls} & +\includegraphics[width=3.0in]{SCRIPT_FIGURES/Verification/suppression_delayed} \end{tabular*} -\caption[Accumulated-water suppression verification]{CFAST and analytical HRR histories for the eight fires in \ct{e_coefficient.in}.} -\label{fig:e_coefficient} +\caption[Decay-time suppression verification]{CFAST and analytical HRR histories for the eight fires in \ct{suppression.in}.} +\label{fig:suppression} \end{figure} \section{Fire Ignition} diff --git a/Source/CFAST/cfast_structures.f90 b/Source/CFAST/cfast_structures.f90 index 3b3d08c82..abbaccc36 100644 --- a/Source/CFAST/cfast_structures.f90 +++ b/Source/CFAST/cfast_structures.f90 @@ -38,7 +38,6 @@ module cfast_types real(eb) :: obscuration_flaming ! smoke obscuration due to flaming fires (calculated) real(eb) :: obscuration_smolder ! smoke obscuration due to smoldering fires (calculated) real(eb) :: activation_time ! time of detector activation (calculated) - real(eb) :: tau ! characteristice quencing time (calculated) logical :: activated ! true if detector has activated (calculated) logical :: reported ! true if detector activation has already been reported (calculated) logical :: dual_detector ! true if smoke detector with different triggers for flaming and smoldering smoke @@ -54,7 +53,8 @@ module cfast_types integer :: ignition_type ! ignition type for fire (user input) ! (1 = time, 2 = temperature, 3 = heat flux) real(eb) :: ignition_criterion ! ignition criterion for fire. Units depend on ignition type (user input) - real(eb) :: e_coefficient ! water suppression coefficient, m2/(kg s); negative selects legacy tau + real(eb) :: decay_time ! suppression reference decay time constant, s + real(eb) :: spray_density ! suppression reference spray density, mm/s integer :: chemistry_type ! fire type. Currently, only constrained fire (user input) real(eb) :: n_C, n_H, n_O, n_N, n_Cl ! stociometry of the fuel (user input) real(eb) :: chirad ! fraction of fire HRR released as radiation (user input) diff --git a/Source/CFAST/fire.f90 b/Source/CFAST/fire.f90 index 190e36552..71eed0878 100644 --- a/Source/CFAST/fire.f90 +++ b/Source/CFAST/fire.f90 @@ -141,7 +141,7 @@ subroutine interpolate_pyrolysis (ifire,tsec,iroom,mdot_t,area_t,height_t,qdot_t real(eb), intent(out) :: mdot_t, area_t, height_t, qdot_t, hoc_t, n_C, n_H, n_O, n_N, n_Cl, y_soot, y_soot_flaming real(eb), intent(out) :: y_soot_smolder, y_co, y_hcn, y_trace - real(eb) :: xxtime, tdrate, xxtimef, qt, qtf, tfact, factor, tfilter_max, wet_time + real(eb) :: xxtime, tau, xxtimef, qt, qtf, tfact, factor, tfilter_max integer :: id, ifact type(room_type), pointer :: roomptr @@ -178,25 +178,17 @@ subroutine interpolate_pyrolysis (ifire,tsec,iroom,mdot_t,area_t,height_t,qdot_t if (id==0) then ! if a sprinkler is not active then interpolate at current time ifact = 0 - else if (fireptr%e_coefficient>=0._eb) then - dtectptr => detectorinfo(id) - wet_time = max(0._eb,tsec-dtectptr%activation_time) - ! Stored spray density is mm/s, numerically kg/(m2 s) for water at 1000 kg/m3. - ! Integrate delivered water mass and then its suppression rate analytically. - ! Keep xxtime at the current fire age, including for growing or delayed fires. - tfact = exp(-0.5_eb*fireptr%e_coefficient*dtectptr%spray_density*wet_time**2) - ifact = 1 else ! if a sprinkler is active then interpolate at current time ! and when sprinkler first activated. make sure that specified ! heat release rate is the smaller of rate at current time ! and rate at sprinkler activation time*exp( ...) dtectptr => detectorinfo(id) - tdrate = dtectptr%tau + tau = fireptr%decay_time*(fireptr%spray_density/dtectptr%spray_density)**(1.8_eb) xxtimef = dtectptr%activation_time - fireptr%ignition_time call interp(fireptr%t_qdot,fireptr%qdot,fireptr%n_qdot,xxtime,1,qt) call interp(fireptr%t_qdot,fireptr%qdot,fireptr%n_qdot,xxtimef,1,qtf) - tfact = exp(-(xxtime-xxtimef)/tdrate) + tfact = exp(-(xxtime-xxtimef)/tau) if (qt0 then interpret it as a spray density and convert - ! to a characteristic quenching time - ! if tdspray < 0 then interpret abs(tdspray) as the time - ! required to reduce the fire size by 50 per cent - ! if tdspray = 0 then turn the sprinkler off - tdspray = dtectptr%spray_density - if (tdspray>0.0_eb) then - tdrate = 3.0_eb/tdspray**1.8_eb - else if (tdspray<0.0_eb) then - tdrate = abs(tdspray)/log(2.0_eb) - tdspray = (3.0_eb/tdrate)**(1.0_eb/1.8_eb) - else - tdspray = 0.0_eb - tdrate = 1.0e10_eb - dtectptr%quench = .false. - end if - dtectptr%spray_density = tdspray - dtectptr%tau = tdrate + if (dtectptr%spray_density==0._eb) dtectptr%quench = .false. ! set initial ceiling jet and detector link temperatures to ambient if (dtectptr%dtype==smoked) then @@ -607,7 +590,7 @@ subroutine initialize_memory n_detectors = 0 detectorinfo(1:mxdtect)%id = ' ' detectorinfo(1:mxdtect)%rti = default_rti - detectorinfo(1:mxdtect)%spray_density = -300.0_eb + detectorinfo(1:mxdtect)%spray_density = 0.06316_eb detectorinfo(1:mxdtect)%center(1) = -1.0_eb detectorinfo(1:mxdtect)%center(2) = -1.0_eb detectorinfo(1:mxdtect)%center(3) = -3.0_eb/39.37_eb @@ -688,7 +671,8 @@ subroutine initialize_fires fireinfo(1:mxfires)%chemistry_type = 2 fireinfo(1:mxfires)%ignition_type = trigger_by_time fireinfo(1:mxfires)%ignition_criterion = 0.0_eb - fireinfo(1:mxfires)%e_coefficient = -1._eb + fireinfo(1:mxfires)%decay_time = 432.8_eb + fireinfo(1:mxfires)%spray_density = 0.06316_eb fireinfo(1:mxfires)%ignition_time = 0.0_eb fireinfo(1:mxfires)%ignited = .false. fireinfo(1:mxfires)%reported = .false. diff --git a/Source/CFAST/input_namelist.f90 b/Source/CFAST/input_namelist.f90 index d2f6c51d2..bb6933ee5 100644 --- a/Source/CFAST/input_namelist.f90 +++ b/Source/CFAST/input_namelist.f90 @@ -917,7 +917,7 @@ subroutine read_devc (lu) ! if spray density is negative, it's the half-life of the heat release rate and spray density will calculated ! if spray density is zero, turn off the sprinkler if (spray_density < 0.0_eb) then - dtectptr%spray_density = spray_density + dtectptr%spray_density = (3._eb/(abs(spray_density)/log(2._eb)))**(1._eb/1.8_eb) elseif (spray_density > 0.0_eb) then dtectptr%spray_density = spray_density*1000.0_eb else @@ -972,7 +972,7 @@ subroutine set_defaults rti = default_rti setpoint = -1001._eb setpoints = (/-1001._eb, -1001._eb/) - spray_density = -300.0_eb + spray_density = 6.316e-5_eb adiabatic_target = .false. convection_coefficients(:) = 0._eb @@ -1102,11 +1102,11 @@ subroutine read_fire (lu) type(fire_type), pointer :: fireptr type(target_type), pointer :: targptr - real(eb) :: e_coefficient, setpoint + real(eb) :: decay_time, spray_density, setpoint character(len=64) :: comp_id, devc_id, fire_id, id, ignition_criterion character(len=128) :: fyi real(eb), dimension(2) :: location - namelist /FIRE/ comp_id, devc_id, e_coefficient, fire_id, fyi, id, ignition_criterion, location, setpoint + namelist /FIRE/ comp_id, decay_time, devc_id, fire_id, fyi, id, ignition_criterion, location, setpoint, spray_density tmpcond = 0.0 @@ -1174,16 +1174,12 @@ subroutine read_fire (lu) fireptr%id = id fireptr%fyi = fyi fireptr%fire_id = fire_id - if (.not.ieee_is_finite(e_coefficient)) then - errormessage = '***Error in FIRE: E_COEFFICIENT must be finite and nonnegative for '//trim(id) - call cfastexit('read_fire',9) - else if (e_coefficient<0._eb) then - errormessage = '***Error in FIRE: E_COEFFICIENT must be nonnegative for '//trim(id) + if (decay_time<0._eb) then + errormessage = '***Error in FIRE: DECAY_TIME must be nonnegative for '//trim(id) call cfastexit('read_fire',9) end if - ! A missing input retains the legacy model; an explicit zero disables attenuation. - fireptr%e_coefficient = -1._eb - if (e_coefficient MechanicalVent: def fire_from_fields(fields: dict[str, list[Any]]) -> FireDefinition: location = number_vector(fields, "LOCATION", [2.5, 2.5], length=2) - e_coefficient = None - if "E_COEFFICIENT" in fields: - values = fields["E_COEFFICIENT"] + suppression = {} + for name, default in (("DECAY_TIME", 432.8), ("SPRAY_DENSITY", 6.316e-5)): + values = fields.get(name, [default]) if (len(values) != 1 or isinstance(values[0], bool) or not isinstance(values[0], (int, float)) - or not math.isfinite(values[0]) or values[0] < 0): - raise ValueError("E_COEFFICIENT must be a finite, nonnegative number.") - e_coefficient = float(values[0]) + or not math.isfinite(values[0]) or values[0] <= 0): + raise ValueError(f"{name} must be a finite, positive number.") + suppression[name.lower()] = float(values[0]) return FireDefinition( id=required_string(fields, "ID", "fire ID"), @@ -1189,7 +1190,7 @@ def fire_from_fields(fields: dict[str, list[Any]]) -> FireDefinition: x_position=location[0], y_position=location[1], fyi=string_field(fields, "FYI", ""), - e_coefficient=e_coefficient, + **suppression, ) diff --git a/Source/CeditQt/cfast_writer.py b/Source/CeditQt/cfast_writer.py index 4a41c1b2b..2a15e19f9 100644 --- a/Source/CeditQt/cfast_writer.py +++ b/Source/CeditQt/cfast_writer.py @@ -529,10 +529,10 @@ def material_is_defined(material_id: str) -> bool: raise ValueError("At least one fire property definition is required.") for fire in case.fires: - if fire.e_coefficient is not None and ( - not math.isfinite(fire.e_coefficient) or fire.e_coefficient < 0.0 - ): - raise ValueError(f"Fire {fire.id!r}: E_COEFFICIENT must be finite and nonnegative.") + for name in ("decay_time", "spray_density"): + value = getattr(fire, name) + if not math.isfinite(value) or value <= 0.0: + raise ValueError(f"Fire {fire.id!r}: {name.upper()} must be finite and positive.") if fire.comp_id not in compartment_ids: raise ValueError( f"Fire {fire.id!r}: compartment {fire.comp_id!r} does not exist." @@ -958,16 +958,16 @@ def write_cfast_input(case: CfastCase, path: str | Path) -> None: fields = [ f"ID = {cfast_string(fire.id)}", f"COMP_ID = {cfast_string(fire.comp_id)}", + f"DECAY_TIME = {cfast_number(fire.decay_time)}", f"FIRE_ID = {cfast_string(fire.fire_property_id)}", f"IGNITION_CRITERION = {cfast_string(fire.ignition_criterion.upper())}", f"SETPOINT = {cfast_number(fire.setpoint)}", f"LOCATION = {cfast_vector((fire.x_position, fire.y_position))}", + f"SPRAY_DENSITY = {cfast_number(fire.spray_density)}", ] if fire.target: fields.append(f"DEVC_ID = {cfast_string(fire.target)}") - if fire.e_coefficient is not None: - fields.append(f"E_COEFFICIENT = {cfast_number(fire.e_coefficient)}") if fire.fyi: fields.append(f"FYI = {cfast_string(fire.fyi)}") diff --git a/Source/CeditQt/run_verification_ui_tests.py b/Source/CeditQt/run_verification_ui_tests.py index 07062c987..c79ddce7d 100644 --- a/Source/CeditQt/run_verification_ui_tests.py +++ b/Source/CeditQt/run_verification_ui_tests.py @@ -45,10 +45,10 @@ "Sprinkler/sprinkler_1_compartments.csv", "Sprinkler/sprinkler_1_devices.csv", ], - "e_coefficient.py": [ - "Sprinkler/e_coefficient_compartments.csv", - "Sprinkler/e_coefficient_devices.csv", - "Sprinkler/e_coefficient.smv", + "suppression.py": [ + "Sprinkler/suppression_compartments.csv", + "Sprinkler/suppression_devices.csv", + "Sprinkler/suppression.smv", ], "target_2.py": [ "Target/target_2_devices.csv", @@ -223,14 +223,14 @@ def select_cases(verification_root: Path, requested: list[str]) -> list[Path]: return cases selected: list[Path] = [] - requested_normalized = [value.lower() for value in requested] + requested_normalized = [value.replace("\\", "/").lower() for value in requested] for path in cases: - names = {path.name.lower(), path.stem.lower(), str(path.relative_to(verification_root)).lower()} + names = {path.name.lower(), path.stem.lower(), path.relative_to(verification_root).as_posix().lower()} if any(value in names for value in requested_normalized): selected.append(path) - missing = sorted(set(requested_normalized) - {item for path in selected for item in {path.name.lower(), path.stem.lower(), str(path.relative_to(verification_root)).lower()}}) + missing = sorted(set(requested_normalized) - {item for path in selected for item in {path.name.lower(), path.stem.lower(), path.relative_to(verification_root).as_posix().lower()}}) if missing: raise RuntimeError("No Verification .in case matched: " + ", ".join(missing)) @@ -770,6 +770,7 @@ def check_verification_case_discovery(): path.touch() assert select_cases(root, []) == paths assert select_cases(root, ["NRC_Users_Guide/Example/nrc.in"]) == [paths[1]] + assert select_cases(root, [r"NRC_Users_Guide\Example\nrc.in"]) == [paths[1]] assert select_cases(root, ["nrc"]) == [paths[1]] @@ -1707,7 +1708,7 @@ def same_values(expected, actual, path="case"): def check_pound_mass_units(): from units import ( - DENSITY, E_COEFFICIENT, HOC, MASS, MASS_FRACTION, MASS_LOSS, + DENSITY, HOC, MASS, MASS_FRACTION, MASS_LOSS, SPECIFIC_EXTINCTION, SPECIFIC_HEAT, format_value, parse_value, unit_system, ) @@ -1724,7 +1725,6 @@ def check_pound_mass_units(): (HOC, "BTU/lbm", 1.05435 / 0.4535924), (SPECIFIC_HEAT, "BTU/(lbm F)", 1.05435 / (0.4535924 * 5 / 9)), (SPECIFIC_EXTINCTION, "ft2/lbm", 0.3048 ** 2 / 0.4535924), - (E_COEFFICIENT, "ft2/(lbm s)", 0.3048 ** 2 / 0.4535924), ): for spelling in (label, label.replace("lbm", "lb")): assert math.isclose(parse_value(kind, f"1 {spelling}", "Mass quantity"), expected) @@ -2341,25 +2341,30 @@ def check_constant_fire_rewrite(app: QApplication): assert reopened.fires == expected.fires -def check_fire_e_coefficient(): +def check_fire_suppression_parameters(): from cfast_reader import fire_from_fields, parse_namelists, read_cfast_input from cfast_writer import validate_case, write_cfast_input - from units import E_COEFFICIENT, LENGTH, MASS, TIME, format_value, parse_value, unit_system + from units import LENGTH, MASS, TIME, VELOCITY, format_value, unit_system root = find_repo_root(Path(__file__)) - case = read_cfast_input(root / "Verification/Sprinkler/e_coefficient.in") - assert [fire.e_coefficient for fire in case.fires] == [1, 2, 0, None, 1, 0.5, 0.1, 1] - for value in (-1, float("inf"), float("nan"), "invalid", True): - try: - fire_from_fields({"E_COEFFICIENT": [value]}) - except ValueError as exc: - assert "E_COEFFICIENT" in str(exc) - else: - raise AssertionError("Invalid E_COEFFICIENT must not silently select the legacy model") + case = read_cfast_input(root / "Verification/Sprinkler/suppression.in") + expected_decay_times = [30, 15, 1000, 432.8, 30, 60, 300, 30] + assert [fire.decay_time for fire in case.fires] == expected_decay_times + assert all(fire.spray_density == 6.316e-5 for fire in case.fires) + assert "suppression.py" in HELPER_SCRIPTS + assert all((root / "Utilities/Python/scripts" / script).is_file() for script in HELPER_SCRIPTS) + for name in ("DECAY_TIME", "SPRAY_DENSITY"): + for values in ([], [1, 2], [0], [-1], [float("inf")], [float("nan")], ["invalid"], [True]): + try: + fire_from_fields({name: values}) + except ValueError as exc: + assert name in str(exc) + else: + raise AssertionError(f"Invalid {name} accepted: {values}") original_units = dict(unit_system.selected) window = CeditMainWindow() try: - with tempfile.TemporaryDirectory(prefix="cedit-e-coefficient-") as directory: + with tempfile.TemporaryDirectory(prefix="cedit-suppression-") as directory: path = Path(directory) / "saved.in" for indices in ((0, 0, 0), (3, 2, 1)): unit_system.set_index(LENGTH, indices[0]) @@ -2368,34 +2373,61 @@ def check_fire_e_coefficient(): window.load_case(case) tab = window.fires_tab tab.select_fire(0) - assert tab.e_coefficient_edit.text() == format_value(E_COEFFICIENT, 1) - assert math.isclose(parse_value(E_COEFFICIENT, "1 m2/(kg s)", "E"), 1) - tab.e_coefficient_edit.setText("0 m2/(kg s)") + assert tab.decay_time_edit.parentWidget().title() == "Suppression Parameters" + assert tab.spray_density_edit.parentWidget() is tab.decay_time_edit.parentWidget() + assert tab.decay_time_edit.text() == format_value(TIME, 30) + assert tab.spray_density_edit.text() == format_value(VELOCITY, 6.316e-5) + tab.decay_time_edit.setText("2 min") + tab.spray_density_edit.setText("0.0002 m/s") tab.select_fire(1) - assert tab.e_coefficient_edit.text() == format_value(E_COEFFICIENT, 2) - assert tab.fires[0].e_coefficient == 0 - tab.e_coefficient_edit.clear() - # Changing the shared definition must not change the instance's coefficient. + assert tab.decay_time_edit.text() == format_value(TIME, 15) + assert tab.fires[0].decay_time == 120 + assert math.isclose(tab.fires[0].spray_density, 0.0002) + tab.decay_time_edit.clear() + tab.spray_density_edit.clear() + # Blank fields restore defaults; shared fuel properties do not own suppression. tab.fire_property_combo.setCurrentText("Growing burner") - assert tab.fires[1].e_coefficient is None + assert tab.fires[1].decay_time == 432.8 + assert tab.fires[1].spray_density == 6.316e-5 assert window.write_case_to_path(path) == path records = [record for record in parse_namelists(path.read_text()) if record.name == "FIRE"] - assert records[0].fields["E_COEFFICIENT"] == [0] - assert "E_COEFFICIENT" not in records[1].fields - assert "E_COEFFICIENT" not in records[3].fields + assert records[0].fields["DECAY_TIME"] == [120] + assert records[0].fields["SPRAY_DENSITY"] == [0.0002] + assert records[1].fields["DECAY_TIME"] == [432.8] + assert records[1].fields["SPRAY_DENSITY"] == [6.316e-5] + assert records[3].fields["DECAY_TIME"] == [432.8] reopened = read_cfast_input(path) - assert [fire.e_coefficient for fire in reopened.fires] == [0, None, 0, None, 1, 0.5, 0.1, 1] - assert case.fires[0].e_coefficient == 1 # Editor owns a copy. + assert len(reopened.fires) == len(tab.fires) + for saved_fire, edited_fire in zip(reopened.fires, tab.fires): + assert saved_fire.id == edited_fire.id + assert math.isclose(saved_fire.decay_time, edited_fire.decay_time, rel_tol=1e-6) + assert math.isclose(saved_fire.spray_density, edited_fire.spray_density, rel_tol=1e-6) + assert [fire.decay_time for fire in reopened.fires] == [120, 432.8, 1000, 432.8, 30, 60, 300, 30] + assert [fire.decay_time for fire in case.fires] == expected_decay_times # Editor owns a copy. + + for edit in (tab.decay_time_edit, tab.spray_density_edit): + original_text = edit.text() + for invalid_text in ("0", "-1", "1e999", "invalid"): + edit.setText(invalid_text) + try: + tab.save_current_fire_only() + except ValueError: + pass + else: + raise AssertionError(f"Invalid suppression input accepted: {invalid_text}") + edit.setText(original_text) + + for name, default in (("decay_time", 432.8), ("spray_density", 6.316e-5)): + for invalid in (0, -1, float("inf"), float("nan")): + setattr(case.fires[0], name, invalid) + try: + validate_case(case) + except ValueError as exc: + assert name.upper() in str(exc) + else: + raise AssertionError(f"Invalid {name} was accepted") + setattr(case.fires[0], name, default) - for invalid in (-1, float("inf"), float("nan")): - case.fires[0].e_coefficient = invalid - try: - validate_case(case) - except ValueError as exc: - assert "E_COEFFICIENT" in str(exc) - else: - raise AssertionError("Invalid E_COEFFICIENT was accepted") - case.fires[0].e_coefficient = 1 write_cfast_input(case, path) assert read_cfast_input(path).fires == case.fires finally: @@ -2436,7 +2468,7 @@ def main() -> int: check_fire_import() check_unused_fire_definitions() check_constant_fire_rewrite(app) - check_fire_e_coefficient() + check_fire_suppression_parameters() print("General editor checks passed", flush=True) if args.mode == "rewrite": diff --git a/Source/CeditQt/tabs/fires_tab.py b/Source/CeditQt/tabs/fires_tab.py index 81270f4f4..0a737bc79 100644 --- a/Source/CeditQt/tabs/fires_tab.py +++ b/Source/CeditQt/tabs/fires_tab.py @@ -4,6 +4,7 @@ import csv import html import io +import math from pathlib import Path from PySide6.QtCore import QByteArray, QMimeData, Qt, Signal @@ -37,7 +38,6 @@ from table_widgets import HoverEditTableWidget, UnitLineEdit, UnitTableWidget from units import ( AREA, - E_COEFFICIENT, HEAT_FLUX, HOC, HRR, @@ -45,6 +45,7 @@ MASS_FRACTION, TEMPERATURE, TIME, + VELOCITY, display_value, format_number, format_value, @@ -393,11 +394,14 @@ def __init__(self, parent=None): self.ignition_combo = QComboBox() self.ignition_combo.addItems(["TIME", "TEMPERATURE", "FLUX"]) self.setpoint_edit = UnitLineEdit() - self.e_coefficient_edit = UnitLineEdit() - self.e_coefficient_edit.setPlaceholderText("Blank: legacy sprinkler suppression") - self.e_coefficient_edit.setToolTip( - "Optional water suppression coefficient for this fire. Blank uses the legacy model; " - "zero disables sprinkler attenuation. Positive values use accumulated water exposure." + self.decay_time_edit = UnitLineEdit() + self.decay_time_edit.setToolTip( + "DECAY_TIME: empirical fire suppression decay time constant. Default: 432.8 s." + ) + self.spray_density_edit = UnitLineEdit() + self.spray_density_edit.setToolTip( + "SPRAY_DENSITY: reference spray density for suppression of this burning object " + "or commodity class. Default: 6.316e-5 m/s." ) self.target_combo = QComboBox() self.target_combo.setEditable(True) @@ -522,11 +526,18 @@ def build_left_editor(self): alignment=Qt.AlignmentFlag.AlignRight, ) fire_layout.addWidget(self.fire_property_combo, 5, 1, 1, 3) - self.e_coefficient_label = QLabel(f"E Coefficient\n({unit_label(E_COEFFICIENT)}):") - fire_layout.addWidget(self.e_coefficient_label, 6, 0, alignment=Qt.AlignmentFlag.AlignRight) - fire_layout.addWidget(self.e_coefficient_edit, 6, 1, 1, 3) fire_group.setLayout(fire_layout) + suppression_group = QGroupBox("Suppression Parameters") + suppression_layout = QGridLayout() + self.decay_time_label = QLabel(f"Decay Time Constant ({unit_label(TIME)}):") + self.spray_density_label = QLabel(f"Spray Density ({unit_label(VELOCITY)}):") + suppression_layout.addWidget(self.decay_time_label, 0, 0, alignment=Qt.AlignmentFlag.AlignRight) + suppression_layout.addWidget(self.decay_time_edit, 0, 1) + suppression_layout.addWidget(self.spray_density_label, 1, 0, alignment=Qt.AlignmentFlag.AlignRight) + suppression_layout.addWidget(self.spray_density_edit, 1, 1) + suppression_group.setLayout(suppression_layout) + property_group = QGroupBox("Fuel Properties") property_layout = QGridLayout() property_layout.addWidget( @@ -563,6 +574,7 @@ def build_left_editor(self): property_group.setLayout(property_layout) layout.addWidget(fire_group) + layout.addWidget(suppression_group) layout.addWidget(property_group) layout.addStretch(1) widget.setLayout(layout) @@ -584,7 +596,8 @@ def build_right_editor(self): def connect_editor_signals(self): line_edits = [ - self.e_coefficient_edit, + self.decay_time_edit, + self.spray_density_edit, self.fire_id_edit, self.x_position_edit, self.y_position_edit, @@ -886,7 +899,8 @@ def clear_editor(self): self.loading = True try: for widget in [ - self.e_coefficient_edit, + self.decay_time_edit, + self.spray_density_edit, self.fire_id_edit, self.x_position_edit, self.y_position_edit, @@ -919,9 +933,8 @@ def load_editor(self, index: int): prop = self.find_property(fire.fire_property_id) self.fire_id_edit.setText(fire.id) - self.e_coefficient_edit.setText( - "" if fire.e_coefficient is None else format_value(E_COEFFICIENT, fire.e_coefficient) - ) + self.decay_time_edit.setText(format_value(TIME, fire.decay_time)) + self.spray_density_edit.setText(format_value(VELOCITY, fire.spray_density)) set_combo_text(self.compartment_combo, fire.comp_id) self.x_position_edit.setText(format_value(LENGTH, fire.x_position)) self.y_position_edit.setText(format_value(LENGTH, fire.y_position)) @@ -1088,11 +1101,12 @@ def save_current_fire_only(self): if fire is None: return - text = self.e_coefficient_edit.text().strip() - coefficient = parse_value(E_COEFFICIENT, text, "E Coefficient") if text else None - if coefficient is not None and coefficient < 0.0: - raise ValueError("E Coefficient must be nonnegative.") - fire.e_coefficient = coefficient + decay_time = parse_value(TIME, self.decay_time_edit.text(), "Decay Time Constant", 432.8) + spray_density = parse_value(VELOCITY, self.spray_density_edit.text(), "Spray Density", 6.316e-5) + if any(not math.isfinite(value) or value <= 0.0 for value in (decay_time, spray_density)): + raise ValueError("Suppression parameters must be finite and positive.") + fire.decay_time = decay_time + fire.spray_density = spray_density fire.id = self.fire_id_edit.text().strip() or fire.id fire.comp_id = ( self.compartment_combo.currentText().strip() @@ -1314,7 +1328,8 @@ def unique_id(name: str, used: set[str]) -> str: def refresh_unit_labels(self): self.ramp_table.setHorizontalHeaderLabels(ramp_headers()) - self.e_coefficient_label.setText(f"E Coefficient\n({unit_label(E_COEFFICIENT)}):") + self.decay_time_label.setText(f"Decay Time Constant ({unit_label(TIME)}):") + self.spray_density_label.setText(f"Spray Density ({unit_label(VELOCITY)}):") def add_to_case(self, case: CfastCase): self.save_current_editor() diff --git a/Source/CeditQt/units.py b/Source/CeditQt/units.py index a85113917..47f11b3e8 100644 --- a/Source/CeditQt/units.py +++ b/Source/CeditQt/units.py @@ -25,7 +25,6 @@ DENSITY = "density" HRR = "hrr" HOC = "hoc" -E_COEFFICIENT = "e_coefficient" SPECIFIC_EXTINCTION = "specific_extinction" HOG = "hog" HEAT_FLUX = "heat_flux" @@ -355,11 +354,6 @@ def refresh(self) -> None: SPECIFIC_EXTINCTION: Conversion( length.multiplier ** 2 / mass.multiplier, 0.0, f"{l_area}/{l_mass}", ), - E_COEFFICIENT: Conversion( - length.multiplier ** 2 / (mass.multiplier * time.multiplier), - 0.0, - f"{l_area}/({l_mass} {l_time})", - ), HOG: Conversion( energy.multiplier / mass.multiplier, 0.0, @@ -526,11 +520,6 @@ def explicit_unit_lookup(kind: str) -> dict[str, Conversion]: label = f"{power_label(length.label, 2)}/{mass.label}" register_unit(lookup, label, Conversion( length.multiplier ** 2 / mass.multiplier, 0.0, label)) - if kind == E_COEFFICIENT: - for time in BASE_UNITS[TIME]: - label = f"{power_label(length.label, 2)}/({mass.label} {time.label})" - register_unit(lookup, label, Conversion( - length.multiplier ** 2 / (mass.multiplier * time.multiplier), 0.0, label)) if kind == DENSITY: volume_label = power_label(length.label, 3) register_unit( diff --git a/Utilities/CI/README.md b/Utilities/CI/README.md index 17c85b383..1d9c75ca1 100644 --- a/Utilities/CI/README.md +++ b/Utilities/CI/README.md @@ -12,7 +12,7 @@ Utilities/CI/Run_CFAST_Cases.sh --suite Validation -I intel -q batch Utilities/CI/Run_CFAST_Cases.sh --suite Verification --test-UI -q batch ``` -Use `-d` for debug, `-m 2` to stop after two iterations, `-s` to request stops, `-t` to report saved timings, `--case e_coefficient.in` to select a case, and `-v` to print job scripts without submitting them. `-e` selects an explicit executable. The `Verification/scripts/Run_CFAST_Cases.sh` and Validation counterpart forward to this shared runner. +Use `-d` for debug, `-m 2` to stop after two iterations, `-s` to request stops, `-t` to report saved timings, `--case suppression.in` to select a case, and `-v` to print job scripts without submitting them. `-e` selects an explicit executable. The `Verification/scripts/Run_CFAST_Cases.sh` and Validation counterpart forward to this shared runner. Suite execution is local by default; specify `-q partition` to submit to Slurm. Cases run sequentially, write the same per-case logs and exit files, and stop the suite on the first failure. For example, from `Verification/scripts`, run `./Run_CFAST_Cases.sh -e ../../Build/CFAST/gnu_macos/cfast8_macos`, or use `-I gnu` to select the standard GNU build for your platform. Executable paths are resolved relative to the caller's working directory; executable names on `PATH` also work, but interactive shell aliases do not. The single-case launcher uses the same rule: local execution unless `-q partition` is supplied. diff --git a/Utilities/Python/CFAST_verification_dataplot_inputs.csv b/Utilities/Python/CFAST_verification_dataplot_inputs.csv index 27d586c86..b1e4bcec8 100644 --- a/Utilities/Python/CFAST_verification_dataplot_inputs.csv +++ b/Utilities/Python/CFAST_verification_dataplot_inputs.csv @@ -43,11 +43,11 @@ d,surface_opened_fraction_1,Ventilation/surface_opened_fraction_1.csv,1,2,Time,L d,surface_opened_fraction_1,Ventilation/surface_opened_fraction_1.csv,1,2,Time,UROF_2|ULOF_2|LROF_2|LLOF_2|FOF_2,Upper_Right(Expected)|Upper_Left(Expected)|Lower_Right(Expected)|Lower_Left(Expected)|Floor(Expected),ko|ro|go|bo|yo,0,100000,,0,100000,0,Ventilation/surface_opened_fraction_1_diagnostics.csv,1,5,Time,UROF_2|ULOF_2|LROF_2|LLOF_2|FOF_2,Upper_Right(CFAST)|Upper_Left(CFAST)|Lower_Right(CFAST)|Lower_Left(CFAST)|Floor(CFAST),k-|r-|g-|b-|y-,0,100000,,0,100000,0,SOF Compartment_2,Time (s),Surface Opening Fraction (SOF),0,1,1,0,0.5,1,no,0.55 0.90,NorthWest,,1,Ventilation/surface_opened_fraction_1_git.txt,linear,SCRIPT_FIGURES/Verification/surface_opening_fraction_3,Absolute,max,0.1,Energy,md,m d,surface_opened_fraction_1,Ventilation/surface_opened_fraction_1.csv,1,2,Time,COF_3|ULOF_3|LLOF_3,Ceiling(Expected)|Upper_Left(Expected)|Lower_Left(Expected),ko|ro|go,0,100000,,0,100000,0,Ventilation/surface_opened_fraction_1_diagnostics.csv,1,5,Time,COF_3|ULOF_3|LLOF_3,Ceiling(CFAST)|Upper_Left(CFAST)|Lower_Left(CFAST),k-|r-|g-,0,100000,,0,100000,0,SOF Compartment_3,Time (s),Surface Opening Fraction (SOF),0,1,1,0,0.5,1,no,0.55 0.90,NorthWest,,1,Ventilation/surface_opened_fraction_1_git.txt,linear,SCRIPT_FIGURES/Verification/surface_opening_fraction_4,Absolute,max,0.1,Energy,md,m d,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,MFLOW,Flow (Expected),ko,10,100000,,10,100000,0,Ventilation/Leakage_1_vents.csv,1,5,Time,MT_Outside_l_outflow_1,Compartment 1,k-,0,100000,,0,100000,0,Leakage,Time (min),Mass Flow (kg/s),0,30,1,0,0.08,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_1_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Flow,Relative,max,0.1,Mass,md,m -f,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,MFLOW,blank,ko,10,100000,,10,100000,0,Ventilation/Leakage_1_vents.csv,1,5,Time,MT_Outside_l_outflow_2,Compartment 2,r-,0,100000,,0,100000,0,Leakage,Time (min),Mass Flow (kg/s),0,30,1,0,0.08,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_2_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Flow,Relative,max,0.1,Mass,md,m -f,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,MFLOW,blank,ko,10,100000,,10,100000,0,Ventilation/Leakage_1_vents.csv,1,5,Time,MT_Outside_l_outflow_3,Compartment 3,g-,0,100000,,0,100000,0,Leakage,Time (min),Mass Flow (kg/s),0,30,1,0,0.08,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_2_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Flow,Relative,max,0.1,Mass,md,m -d,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,P_FLOOR,Pressure (Expected),ko,10,100000,,10,100000,0,Ventilation/Leakage_1_compartments.csv,1,5,Time,PRS_1,Compartment 1,k-,0,100000,,0,100000,0,Leakage,Time (min),Pressure (Pa),0,30,1,0,10,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_2_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Pressure,Relative,max,0.1,Mass,md,m -f,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,P_FLOOR,blank,ko,10,100000,,10,100000,0,Ventilation/Leakage_1_compartments.csv,1,5,Time,PRS_2,Compartment 2,r-,0,100000,,0,100000,0,Leakage,Time (min),Pressure (Pa),0,30,1,0,10,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_2_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Pressure,Relative,max,0.1,Mass,md,m -f,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,P_FLOOR,blank,ko,10,100000,,10,100000,0,Ventilation/Leakage_1_compartments.csv,1,5,Time,PRS_3,Compartment 3,g-,0,100000,,0,100000,0,Leakage,Time (min),Pressure (Pa),0,30,1,0,10,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_2_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Pressure,Relative,max,0.1,Mass,md,m +f,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,MFLOW,blank,ko,10,100000,,10,100000,0,Ventilation/Leakage_1_vents.csv,1,5,Time,MT_Outside_l_outflow_2,Compartment 2,r-,0,100000,,0,100000,0,Leakage,Time (min),Mass Flow (kg/s),0,30,1,0,0.08,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_1_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Flow,Relative,max,0.1,Mass,md,m +f,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,MFLOW,blank,ko,10,100000,,10,100000,0,Ventilation/Leakage_1_vents.csv,1,5,Time,MT_Outside_l_outflow_3,Compartment 3,g-,0,100000,,0,100000,0,Leakage,Time (min),Mass Flow (kg/s),0,30,1,0,0.08,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_1_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Flow,Relative,max,0.1,Mass,md,m +d,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,P_FLOOR,Pressure (Expected),ko,10,100000,,10,100000,0,Ventilation/Leakage_1_compartments.csv,1,5,Time,PRS_1,Compartment 1,k-,0,100000,,0,100000,0,Leakage,Time (min),Pressure (Pa),0,30,1,0,10,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_1_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Pressure,Relative,max,0.1,Mass,md,m +f,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,P_FLOOR,blank,ko,10,100000,,10,100000,0,Ventilation/Leakage_1_compartments.csv,1,5,Time,PRS_2,Compartment 2,r-,0,100000,,0,100000,0,Leakage,Time (min),Pressure (Pa),0,30,1,0,10,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_1_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Pressure,Relative,max,0.1,Mass,md,m +f,Leakage_1,Ventilation/Leakage_Flow.csv,1,3,Time,P_FLOOR,blank,ko,10,100000,,10,100000,0,Ventilation/Leakage_1_compartments.csv,1,5,Time,PRS_3,Compartment 3,g-,0,100000,,0,100000,0,Leakage,Time (min),Pressure (Pa),0,30,1,0,10,1,no,0.05 0.90,SouthEast,,1,Ventilation/Leakage_1_git.txt,linear,SCRIPT_FIGURES/Verification/Leakage_1_Pressure,Relative,max,0.1,Mass,md,m s,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, d,radiation_1,Radiation/radiation_1.csv,1,2,Time,Temp,Plate Temp (Expected),ko,0,100000,,0,100000,20,Radiation/radiation_1_devices.csv,1,5,Time,TRGSURT_1,Plate Temp (CFAST),k-,0,100000,,0,100000,20,Target Surface Temperature (radiation_1),Time (min),Temperature (°C),0,60,60,20,26,1,no,0.05 0.90,SouthEast,,1,Radiation/radiation_1_git.txt,linear,SCRIPT_FIGURES/Verification/radiation_1_temp,Relative,max,0.1,Energy,md,m d,radiation_2,Radiation/radiation_2.csv,1,2,Time,rod temp|plate temp|thin plate temp,Rod (Expected)|Thick Plate (Expected)|Thin Plate (Expected),ko|ro|go,0,100000,,0,100000,20,Radiation/radiation_2_devices.csv,1,5,Time,TRGSURT_1|TRGSURT_2|TRGSURT_3,Rod (CFAST)|Thick Plate (CFAST)|Thin Plate (CFAST),k-|r-|g-,0,100000,,0,100000,20,Target Surface Temperature (radiation_2),Time (min),Temperature (°C),0,60,60,0,650,1,no,0.05 0.90,SouthEast,,1,Radiation/radiation_2_git.txt,linear,SCRIPT_FIGURES/Verification/radiation_2_temp,Relative,max,0.1,Energy,md,m @@ -60,10 +60,10 @@ d,radiation_5,Radiation/radiation_5.csv,1,2,X,ABSORPTIVITY (TOP),Hemispherical a s,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, d,sprinkler_1,Sprinkler/sprinkler_1.csv,1,2,TimeLinkEx,TempLinkEx,T$_{\rm L}$ (Expected),ko,0,100000,,0,100000,0,Sprinkler/sprinkler_1_devices.csv,1,5,Time,SENST_1,T$_{\rm L}$ (CFAST),k-,0,100000,,0,100000,0,Sprinkler Link Temperature (sprinkler_1),Time (min),Temperature (°C),0,85,60,0,150,1,no,0.05 0.90,SouthEast,,1,Sprinkler/sprinkler_1_git.txt,linear,SCRIPT_FIGURES/Verification/sprinkler_1_link_temp,Relative,max,0.1,Energy,md,m d,sprinkler_1,Sprinkler/sprinkler_1.csv,1,2,TimeHRREx,HRREx,HRR (Expected),ko,0,100000,,0,100000,0,Sprinkler/sprinkler_1_compartments.csv,1,5,Time,HRR_1,HRR (CFAST),k-,0,100000,,0,100000,0,Fire Suppression (sprinkler_1),Time (min),Heat Release Rate (kW),1,85,60,0,11,1000,no,0.05 0.90,East,,1,Sprinkler/sprinkler_1_git.txt,linear,SCRIPT_FIGURES/Verification/sprinkler_1_HRR,Relative,max,0.1,Energy,md,m -d,e_coefficient,Sprinkler/e_coefficient_expected.csv,1,2,Time,Expected_1|Expected_2,E=1 (Expected)|E=2 (Expected),ko|rs,0,100000,,0,100000,0,Sprinkler/e_coefficient_compartments.csv,1,5,Time,HRR_1|HRR_2,E=1 (CFAST)|E=2 (CFAST),k-|r--,0,100000,,0,100000,0,Room A,Time (s),Heat Release Rate (kW),0,60,1,0,55,1000,no,0.05 0.90,NorthEast,,1,Sprinkler/e_coefficient_git.txt,linear,SCRIPT_FIGURES/Verification/e_coefficient_room_a,N/A,max,0.1,Energy,md,m -d,e_coefficient,Sprinkler/e_coefficient_expected.csv,1,2,Time,Expected_5|Expected_6,Constant (Expected)|Growing (Expected),ko|rs,0,100000,,0,100000,0,Sprinkler/e_coefficient_compartments.csv,1,5,Time,HRR_5|HRR_6,Constant (CFAST)|Growing (CFAST),k-|r--,0,100000,,0,100000,0,Room B,Time (s),Heat Release Rate (kW),0,60,1,0,55,1000,no,0.05 0.90,NorthEast,,1,Sprinkler/e_coefficient_git.txt,linear,SCRIPT_FIGURES/Verification/e_coefficient_room_b,N/A,max,0.1,Energy,md,m -d,e_coefficient,Sprinkler/e_coefficient_expected.csv,1,2,Time,Expected_3|Expected_4|Expected_8,E=0 (Expected)|Legacy (Expected)|Dry room (Expected),ko|rs|g^,0,100000,,0,100000,0,Sprinkler/e_coefficient_compartments.csv,1,5,Time,HRR_3|HRR_4|HRR_8,E=0 (CFAST)|Legacy (CFAST)|Dry room (CFAST),k-|r--|g:,0,100000,,0,100000,0,Controls,Time (s),Heat Release Rate (kW),0,60,1,0,55,1000,no,0.05 0.90,SouthWest,,1,Sprinkler/e_coefficient_git.txt,linear,SCRIPT_FIGURES/Verification/e_coefficient_controls,N/A,max,0.1,Energy,md,m -d,e_coefficient,Sprinkler/e_coefficient_expected.csv,1,2,Time,Expected_7,Delayed (Expected),ko,0,100000,,0,100000,0,Sprinkler/e_coefficient_compartments.csv,1,5,Time,HRR_7,Delayed (CFAST),k-,0,100000,,0,100000,0,Room B: Delayed ignition,Time (s),Heat Release Rate (kW),0,60,1,0,55,1000,no,0.05 0.90,SouthEast,,1,Sprinkler/e_coefficient_git.txt,linear,SCRIPT_FIGURES/Verification/e_coefficient_delayed,N/A,max,0.1,Energy,md,m +d,suppression,Sprinkler/suppression_expected.csv,1,2,Time,Expected_1|Expected_2,30 s (Expected)|15 s (Expected),ko|rs,0,100000,,0,100000,0,Sprinkler/suppression_compartments.csv,1,5,Time,HRR_1|HRR_2,30 s (CFAST)|15 s (CFAST),k-|r-,0,100000,,0,100000,0,Room A,Time (s),Heat Release Rate (kW),0,60,1,0,55,1000,no,0.05 0.90,NorthEast,,1,Sprinkler/suppression_git.txt,linear,SCRIPT_FIGURES/Verification/suppression_room_a,N/A,max,0.1,Energy,md,m +d,suppression,Sprinkler/suppression_expected.csv,1,2,Time,Expected_5|Expected_6,Constant (Expected)|Growing (Expected),ko|rs,0,100000,,0,100000,0,Sprinkler/suppression_compartments.csv,1,5,Time,HRR_5|HRR_6,Constant (CFAST)|Growing (CFAST),k-|r-,0,100000,,0,100000,0,Room B,Time (s),Heat Release Rate (kW),0,60,1,0,55,1000,no,0.05 0.90,NorthEast,,1,Sprinkler/suppression_git.txt,linear,SCRIPT_FIGURES/Verification/suppression_room_b,N/A,max,0.1,Energy,md,m +d,suppression,Sprinkler/suppression_expected.csv,1,2,Time,Expected_3|Expected_4|Expected_8,1000 s (Expected)|Legacy (Expected)|Dry room (Expected),ko|rs|g^,0,100000,,0,100000,0,Sprinkler/suppression_compartments.csv,1,5,Time,HRR_3|HRR_4|HRR_8,1000 s (CFAST)|Legacy (CFAST)|Dry room (CFAST),k-|r-|g-,0,100000,,0,100000,0,Controls,Time (s),Heat Release Rate (kW),0,60,1,0,55,1000,no,0.05 0.90,SouthWest,,1,Sprinkler/suppression_git.txt,linear,SCRIPT_FIGURES/Verification/suppression_controls,N/A,max,0.1,Energy,md,m +d,suppression,Sprinkler/suppression_expected.csv,1,2,Time,Expected_7,Delayed (Expected),ko,0,100000,,0,100000,0,Sprinkler/suppression_compartments.csv,1,5,Time,HRR_7,Delayed (CFAST),k-,0,100000,,0,100000,0,Room B: Delayed ignition,Time (s),Heat Release Rate (kW),0,60,1,0,55,1000,no,0.05 0.90,SouthEast,,1,Sprinkler/suppression_git.txt,linear,SCRIPT_FIGURES/Verification/suppression_delayed,N/A,max,0.1,Energy,md,m s,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, d,Ignition_Test,Fires/fire_ignition.csv,1,2,Time,IGNHRR_1,Ignition Time (s),r--,0,100000,,0,100000,0,Fires/Ignition_Test_compartments.csv,1,5,Time,HRR_2,HRR (CFAST),k-,0,100000,,0,100000,0,Heat Release Rate (Ignition_Test),Time (min),Heat Release Rate (W),0,12,60,0,2500,1,no,0.05 0.90,SouthEast,,1,Fires/Ignition_Test_git.txt,linear,SCRIPT_FIGURES/Verification/fire_ignition_HRR_1,N/A,max,0.1,Energy,md,m d,Ignition_Test,Fires/fire_ignition.csv,1,2,Time,IGNFLUX_1,Ignition Time (s),r--,0,100000,,0,100000,0,Fires/Ignition_Test_devices.csv,1,5,Time,TRGFLXI_1,TRGFLXI_1 (CFAST),k-,0,100000,,0,100000,0,Incident Heat Flux at Fire (Ignition_Test),Time (min),Heat Flux (W/m²),0,12,60,0,12,1,no,0.05 0.90,SouthEast,,1,Fires/Ignition_Test_git.txt,linear,SCRIPT_FIGURES/Verification/fire_ignition_flux_1,N/A,max,0.1,Energy,md,m diff --git a/Utilities/Python/CFAST_verification_script.py b/Utilities/Python/CFAST_verification_script.py index f1b06401e..aaf7eb5ee 100644 --- a/Utilities/Python/CFAST_verification_script.py +++ b/Utilities/Python/CFAST_verification_script.py @@ -32,7 +32,7 @@ def safe_run(script_path, script_args=None): print("radiativefluxes..."); safe_run("./scripts/radiativefluxes.py") print("speciesmass..."); safe_run("./scripts/speciesmass.py") print("sprinkler_1..."); safe_run("./scripts/sprinkler_1.py") -print("e_coefficient..."); safe_run("./scripts/e_coefficient.py", []) +print("suppression..."); safe_run("./scripts/suppression.py", []) print("target_2..."); safe_run("./scripts/target_2.py") print("generate_cedit_figures..."); safe_run("../../Manuals/CFAST_Users_Guide/generate_cedit_figures.py", []) diff --git a/Utilities/Python/scripts/e_coefficient.py b/Utilities/Python/scripts/suppression.py similarity index 77% rename from Utilities/Python/scripts/e_coefficient.py rename to Utilities/Python/scripts/suppression.py index 654348778..bcaeb8d8a 100644 --- a/Utilities/Python/scripts/e_coefficient.py +++ b/Utilities/Python/scripts/suppression.py @@ -1,4 +1,4 @@ -"""Check the multi-room E_COEFFICIENT case against the analytical solution.""" +"""Check the multi-room DECAY_TIME suppression case against the analytical solution.""" from __future__ import annotations import argparse @@ -41,24 +41,26 @@ def verify(case_path: Path): # Fire order and inputs are deliberately fixed to the verification case. names = ["Reference", "Stronger", "No attenuation", "Legacy", "Second room", "Growing", "Delayed", "Dry room"] - coefficients = [1.0, 2.0, 0.0, None, 1.0, 0.5, 0.1, 1.0] - mass_fluxes = [0.1] * 4 + [0.2] * 3 + [0.0] # kg/(m2 s) + decay_times = [30.0, 15.0, 1000.0, 432.8, 30.0, 60.0, 300.0, 30.0] + spray_densities = [0.0001] * 4 + [0.0002] * 3 + [0.0] # m/s + reference_spray_density = 6.3163e-5 # FIRE default, m/s. expected = {} errors = {} output_step = data["Time"][1] - data["Time"][0] - for fire, (name, coefficient, flux) in enumerate(zip(names, coefficients, mass_fluxes), 1): + for fire, (name, decay_time, flux) in enumerate(zip(names, decay_times, spray_densities), 1): values = [] for time in data["Time"]: baseline = 18000 + 600 * time if fire == 6 else 36000.0 if fire == 7 and time < 15: baseline = 0.0 - wet_time = max(0.0, time - activation[1 if fire <= 4 else 2]) if flux else 0.0 - if coefficient is None: - tau = 3.0 / flux ** 1.8 # Legacy correlation uses the numerical mm/s value. - factor = math.exp(-wet_time / tau) - else: - factor = math.exp(-0.5 * coefficient * flux * wet_time ** 2) - values.append(baseline * factor * min(time, 1.0)) # Existing adiabatic startup filter. + if flux: + activation_time = activation[1 if fire <= 4 else 2] + if time >= activation_time: + tau = decay_time * (reference_spray_density / flux) ** 1.8 + # Suppression decays from the prescribed HRR at activation. + activation_hrr = 18000 + 600 * activation_time if fire == 6 else 36000.0 + baseline = min(baseline, activation_hrr * math.exp(-(time - activation_time) / tau)) + values.append(baseline * min(time, 1.0)) # Existing adiabatic startup filter. expected[f"Expected_{fire}"] = values peak = 54000 if fire == 6 else 36000 # 0.2% of unsuppressed peak allows for 0.01 s activation-time output precision. @@ -79,15 +81,14 @@ def verify(case_path: Path): writer = csv.writer(stream) writer.writerow(["Time", *expected]) # One-second samples keep the dataplot analytical markers legible. - stride = max(1, round(1.0 / output_step)) + stride = max(1, round(2.0 / output_step)) writer.writerows(zip(data["Time"][::stride], *(values[::stride] for values in expected.values()))) - print(f"E_COEFFICIENT: all 8 fires passed; maximum normalized error {max(errors.values()):.6g}") return errors if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--case", type=Path, default=REPO_ROOT / "Verification/Sprinkler/e_coefficient.in") + parser.add_argument("--case", type=Path, default=REPO_ROOT / "Verification/Sprinkler/suppression.in") args = parser.parse_args() verify(args.case) diff --git a/Verification/Sprinkler/e_coefficient.in b/Verification/Sprinkler/suppression.in similarity index 83% rename from Verification/Sprinkler/e_coefficient.in rename to Verification/Sprinkler/suppression.in index 06693825d..9e6c40e00 100644 --- a/Verification/Sprinkler/e_coefficient.in +++ b/Verification/Sprinkler/suppression.in @@ -1,9 +1,9 @@ &HEAD VERSION = 7600, TITLE = 'Per-fire water suppression in three rooms' / -!! Suppression by continuously accumulating sprinkler water. +!! Suppression with per-fire decay time constants. !! Shared methane burners: 0.6 m by 0.6 m, 100 kW/m2, hence 36 kW. -!! Room A: E=1, E=2, E=0, and the legacy tau model. +!! Room A: decay times of 30, 15, 1000, and the default 432.8 s. !! Room B: twice the spray density, a growing fire, and a delayed fire. -!! Room C: no active sprinkler; E=1 must leave the prescribed HRR unchanged. +!! Room C: no active sprinkler; prescribed HRR must remain unchanged. &TIME SIMULATION = 60, PRINT = 10, SMOKEVIEW = 10, SPREADSHEET = 0.02 / &INIT INTERIOR_TEMPERATURE = 20, EXTERIOR_TEMPERATURE = 20 / &MISC ADIABATIC = .TRUE., MAX_TIME_STEP = 0.1 / @@ -13,14 +13,14 @@ &VENT ID = 'Door A', TYPE = 'WALL', COMP_IDS = 'Room A','OUTSIDE', WIDTH = 2, BOTTOM = 0, TOP = 3 / &VENT ID = 'Door B', TYPE = 'WALL', COMP_IDS = 'Room B','OUTSIDE', WIDTH = 2, BOTTOM = 0, TOP = 3 / &VENT ID = 'Door C', TYPE = 'WALL', COMP_IDS = 'Room C','OUTSIDE', WIDTH = 2, BOTTOM = 0, TOP = 3 / -&FIRE ID = 'Reference', COMP_ID = 'Room A', FIRE_ID = 'Burner', LOCATION = 2,2, E_COEFFICIENT = 1 / -&FIRE ID = 'Stronger', COMP_ID = 'Room A', FIRE_ID = 'Burner', LOCATION = 7,2, E_COEFFICIENT = 2 / -&FIRE ID = 'No attenuation', COMP_ID = 'Room A', FIRE_ID = 'Burner', LOCATION = 2,7, E_COEFFICIENT = 0 / +&FIRE ID = 'Reference', COMP_ID = 'Room A', FIRE_ID = 'Burner', LOCATION = 2,2, DECAY_TIME = 30 / +&FIRE ID = 'Stronger', COMP_ID = 'Room A', FIRE_ID = 'Burner', LOCATION = 7,2, DECAY_TIME = 15 / +&FIRE ID = 'No attenuation', COMP_ID = 'Room A', FIRE_ID = 'Burner', LOCATION = 2,7, DECAY_TIME = 1000 / &FIRE ID = 'Legacy', COMP_ID = 'Room A', FIRE_ID = 'Burner', LOCATION = 7,7 / -&FIRE ID = 'Second room', COMP_ID = 'Room B', FIRE_ID = 'Burner', LOCATION = 2,2, E_COEFFICIENT = 1 / -&FIRE ID = 'Growing', COMP_ID = 'Room B', FIRE_ID = 'Growing burner', LOCATION = 7,2, E_COEFFICIENT = 0.5 / -&FIRE ID = 'Delayed', COMP_ID = 'Room B', FIRE_ID = 'Burner', LOCATION = 2,7, SETPOINT = 15, E_COEFFICIENT = 0.1 / -&FIRE ID = 'Dry room', COMP_ID = 'Room C', FIRE_ID = 'Burner', LOCATION = 2,2, E_COEFFICIENT = 1 / +&FIRE ID = 'Second room', COMP_ID = 'Room B', FIRE_ID = 'Burner', LOCATION = 2,2, DECAY_TIME = 30 / +&FIRE ID = 'Growing', COMP_ID = 'Room B', FIRE_ID = 'Growing burner', LOCATION = 7,2, DECAY_TIME = 60 / +&FIRE ID = 'Delayed', COMP_ID = 'Room B', FIRE_ID = 'Burner', LOCATION = 2,7, SETPOINT = 15, DECAY_TIME = 300 / +&FIRE ID = 'Dry room', COMP_ID = 'Room C', FIRE_ID = 'Burner', LOCATION = 2,2, DECAY_TIME = 30 / &CHEM ID = 'Burner', CARBON = 1, HYDROGEN = 4, HEAT_OF_COMBUSTION = 50000, RADIATIVE_FRACTION = 0.35 / &TABL ID = 'Burner', LABELS = 'TIME','HRR','HEIGHT','AREA','CO_YIELD','SOOT_YIELD','HCN_YIELD','TRACE_YIELD' / &TABL ID = 'Burner', DATA = 0,36,0.25,0.36,0,0,0,0 / diff --git a/Verification/scripts/CFAST_Cases.sh b/Verification/scripts/CFAST_Cases.sh index 326d9a107..3795a72bd 100755 --- a/Verification/scripts/CFAST_Cases.sh +++ b/Verification/scripts/CFAST_Cases.sh @@ -37,7 +37,7 @@ $RUNCFAST -d Species methane_flame_simple.in $RUNCFAST -d Species species_test.in $RUNCFAST -d Sprinkler sprinkler_1.in -$RUNCFAST -d Sprinkler e_coefficient.in +$RUNCFAST -d Sprinkler suppression.in $RUNCFAST -d Target target_1.in $RUNCFAST -d Target target_2.in