LCOV - code coverage report
Current view: top level - src/postprocessors - ADSpecificImpulse1Phase.C (source / functions) Hit Total Coverage
Test: idaholab/moose thermal_hydraulics: #30301 (3b550b) with base 2ad78d Lines: 77 80 96.2 %
Date: 2025-07-30 13:02:48 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "ADSpecificImpulse1Phase.h"
      11             : #include "SinglePhaseFluidProperties.h"
      12             : #include "Numerics.h"
      13             : #include "THMIndicesVACE.h"
      14             : #include "ADBoundaryFluxBase.h"
      15             : 
      16             : registerMooseObject("ThermalHydraulicsApp", ADSpecificImpulse1Phase);
      17             : 
      18             : InputParameters
      19         114 : ADSpecificImpulse1Phase::validParams()
      20             : {
      21         114 :   InputParameters params = SidePostprocessor::validParams();
      22         228 :   params.addRequiredParam<Real>("p_exit", "Outlet pressure at nozzle exit");
      23         228 :   params.addRequiredParam<UserObjectName>("fp", "Single-phase fluid properties");
      24         228 :   params.addParam<Real>(
      25         228 :       "bisection_tolerance", 1e-4, "Tolerance for bisection to find h(entropy_in, p_out)");
      26         228 :   params.addParam<unsigned int>(
      27             :       "bisection_max_it",
      28         228 :       100,
      29             :       "Maximum number of iterations for bisection to find h(entropy_in, p_out)");
      30         228 :   params.addParam<bool>("cumulative",
      31         228 :                         true,
      32             :                         "If specific impulse is accumulated over timesteps. If false, then "
      33             :                         "instantaneous value is computed");
      34         228 :   params.addCoupledVar("variables", "Single-phase flow variables");
      35         684 :   params.set<std::vector<VariableName>>("variables") = {"rhoA", "rhouA", "rhoEA", "A"};
      36         114 :   params.addClassDescription("Estimates specific impulse from fluid state at a boundary");
      37         114 :   return params;
      38           0 : }
      39             : 
      40          42 : ADSpecificImpulse1Phase::ADSpecificImpulse1Phase(const InputParameters & parameters)
      41             :   : SidePostprocessor(parameters),
      42          42 :     _n_components(THMVACE1D::N_FLUX_INPUTS),
      43          84 :     _boundary_name(getParam<std::vector<BoundaryName>>("boundary")[0]),
      44          42 :     _boundary_uo_name(_boundary_name + ":boundary_uo"),
      45          42 :     _boundary_uo(getUserObjectByName<ADBoundaryFluxBase>(_boundary_uo_name)),
      46          84 :     _p_exit(getParam<Real>("p_exit")),
      47          84 :     _H(getADMaterialPropertyByName<Real>("H")),
      48          42 :     _v(getADMaterialPropertyByName<Real>("v")),
      49          42 :     _e(getADMaterialPropertyByName<Real>("e")),
      50          42 :     _T(getADMaterialPropertyByName<Real>("T")),
      51          42 :     _fp(getUserObject<SinglePhaseFluidProperties>("fp")),
      52          84 :     _tol(getParam<Real>("bisection_tolerance")),
      53          84 :     _max_nit(getParam<unsigned int>("bisection_max_it")),
      54          84 :     _cumulative(getParam<bool>("cumulative")),
      55          84 :     _accumulated_mass_flow_rate(declareRestartableData<Real>("accumulated_mass_flow_rate", 0)),
      56          84 :     _accumulated_thrust(declareRestartableData<Real>("accumulated_thrust", 0)),
      57          42 :     _value(0.0)
      58             : {
      59          42 :   if (_cumulative && !_fe_problem.isTransient())
      60           0 :     paramError("cumulative", "Must be false unless problem is transient");
      61             : 
      62         210 :   for (unsigned int i = 0; i < _n_components; i++)
      63         336 :     _U.push_back(&adCoupledValue("variables", i));
      64          42 : }
      65             : 
      66             : void
      67         150 : ADSpecificImpulse1Phase::threadJoin(const UserObject & y)
      68             : {
      69             :   const ADSpecificImpulse1Phase & pps = static_cast<const ADSpecificImpulse1Phase &>(y);
      70         150 :   _thrust += pps._thrust;
      71         150 :   _mass_flow_rate += pps._mass_flow_rate;
      72         150 : }
      73             : 
      74             : void
      75         800 : ADSpecificImpulse1Phase::initialize()
      76             : {
      77         800 :   _mass_flow_rate = 0;
      78         800 :   _thrust = 0;
      79         800 : }
      80             : 
      81             : Real
      82         650 : ADSpecificImpulse1Phase::getValue() const
      83             : {
      84         650 :   return _value;
      85             : }
      86             : 
      87             : void
      88         650 : ADSpecificImpulse1Phase::finalize()
      89             : {
      90         650 :   gatherSum(_thrust);
      91         650 :   gatherSum(_mass_flow_rate);
      92             : 
      93         650 :   if (_cumulative)
      94             :   {
      95         325 :     _accumulated_thrust += _dt * _thrust;
      96         325 :     _accumulated_mass_flow_rate += _dt * _mass_flow_rate;
      97         325 :     _value = _accumulated_thrust / _accumulated_mass_flow_rate / THM::gravity_const;
      98             :   }
      99             :   else
     100         325 :     _value = _thrust / _mass_flow_rate / THM::gravity_const;
     101         650 : }
     102             : 
     103             : void
     104         400 : ADSpecificImpulse1Phase::execute()
     105             : {
     106         800 :   for (unsigned int qp = 0; qp < _qrule->n_points(); qp++)
     107             :   {
     108         400 :     std::vector<ADReal> U(_n_components);
     109        2000 :     for (unsigned int i = 0; i < _n_components; i++)
     110        1600 :       U[i] = (*_U[i])[qp];
     111             : 
     112         400 :     const auto & flux = _boundary_uo.getFlux(_current_side, _current_elem->id(), U, _normals[qp]);
     113             : 
     114         400 :     _mass_flow_rate += std::abs(MetaPhysicL::raw_value(flux[0]));
     115             : 
     116             :     // get entropy at inlet (= entropy at outlet because process is isentropic)
     117             :     Real entropy_in =
     118         400 :         _fp.s_from_v_e(MetaPhysicL::raw_value(_v[qp]), MetaPhysicL::raw_value(_e[qp]));
     119             : 
     120             :     // compute outlet enthalpy from entropy at inlet (isentropic flow)
     121             :     // and pressure at outlet, need to do bisection because h_from_s_p does not
     122             :     // exist, it is better to work with temperature because s(p, T) is available
     123             : 
     124             :     // upper bound of temperature is the inlet temperature
     125         400 :     Real T_up = MetaPhysicL::raw_value(_T[qp]);
     126             : 
     127             :     // compute entropy associated with T_up
     128         400 :     Real entropy_up = _fp.s_from_p_T(_p_exit, T_up);
     129             : 
     130             :     // lower bound for temperature is 0.1 K
     131             :     Real T_low = 0.1;
     132             : 
     133             :     // compute entropy associated with T_low
     134         400 :     Real entropy_low = _fp.s_from_p_T(_p_exit, T_low);
     135             : 
     136             :     // compute the midpoint temperature
     137         400 :     Real T_mid = 0.5 * (T_up + T_low);
     138             : 
     139             :     // the current guess for entropy
     140         400 :     Real entropy_mid = _fp.s_from_p_T(_p_exit, T_mid);
     141             : 
     142             :     // main bisection loop
     143             :     unsigned int nit = 0;
     144        4400 :     while (std::abs(1 - entropy_mid / entropy_in) > _tol)
     145             :     {
     146             :       // check if we are over/underestimating to select either up/down
     147        4000 :       if ((entropy_mid - entropy_in > 0) == (entropy_up - entropy_low > 0))
     148             :       {
     149             :         T_up = T_mid;
     150             :         entropy_up = entropy_mid;
     151             :       }
     152             :       else
     153             :       {
     154             :         T_low = T_mid;
     155             :         entropy_low = entropy_mid;
     156             :       }
     157             : 
     158             :       // update guess for T_mid
     159        4000 :       T_mid = 0.5 * (T_up + T_low);
     160             : 
     161             :       // update the guess for entropy
     162        4000 :       entropy_mid = _fp.s_from_p_T(_p_exit, T_mid);
     163             : 
     164        4000 :       ++nit;
     165             : 
     166        4000 :       if (nit == _max_nit)
     167             :       {
     168           0 :         mooseDoOnce(mooseWarning("Bisection in ADSpecificImpulse1Phase did not converge"));
     169             :         break;
     170             :       }
     171             :     }
     172             : 
     173             :     // the enthalpy evaluated at _p_exit and T_mid
     174         400 :     Real h_exit = _fp.h_from_p_T(_p_exit, T_mid);
     175             : 
     176             :     // compute outlet speed
     177         400 :     Real vel_exit = std::sqrt(2.0 * (MetaPhysicL::raw_value(_H[qp]) - h_exit));
     178         400 :     _thrust += std::abs(vel_exit * MetaPhysicL::raw_value(flux[0]));
     179             :   }
     180         400 : }

Generated by: LCOV version 1.14