LCOV - code coverage report
Current view: top level - src/materials - PorousFlowHysteresisOrder.C (source / functions) Hit Total Coverage
Test: idaholab/moose porous_flow: #31405 (292dce) with base fef103 Lines: 89 90 98.9 %
Date: 2025-09-04 07:55:56 Functions: 4 4 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 "PorousFlowHysteresisOrder.h"
      11             : #include "Conversion.h"
      12             : 
      13             : registerMooseObject("PorousFlowApp", PorousFlowHysteresisOrder);
      14             : 
      15             : InputParameters
      16       12716 : PorousFlowHysteresisOrder::validParams()
      17             : {
      18       12716 :   InputParameters params = PorousFlowMaterial::validParams();
      19       25432 :   params.addPrivateParam<std::string>("pf_material_type", "hysteresis_order");
      20       25432 :   params.addParam<unsigned>("liquid_phase", 0, "The phase number of the liquid phase");
      21       25432 :   params.addParam<unsigned>(
      22             :       "initial_order",
      23       25432 :       0,
      24             :       "The initial order.  0 means the simulation will begin on the primary drying curve.  1 means "
      25             :       "the simulation will begin on the first-order wetting curve.  2 means the simulation will "
      26             :       "begin on the second-order drying curve, etc.");
      27       12716 :   params.addParam<std::vector<Real>>(
      28             :       "previous_turning_points",
      29       12716 :       std::vector<Real>(),
      30             :       "The turning points (liquid saturation values) at occured prior to the simulation.  There "
      31             :       "must be exactly initial_order of these values.  The first value is the liquid saturation at "
      32             :       "the transition from primary-drying to first-order-wetting; the second value is the liquid "
      33             :       "saturation at the transition from first-order-wetting to second-order-drying; and so on.  "
      34             :       "You must ensure that your initial saturation field is commensurate with the "
      35             :       "previous_turning_points.");
      36       12716 :   params.addClassDescription("Computes hysteresis order for use in hysteretic capillary pressures "
      37             :                              "and relative permeabilities");
      38       12716 :   return params;
      39           0 : }
      40             : 
      41        9871 : PorousFlowHysteresisOrder::PorousFlowHysteresisOrder(const InputParameters & parameters)
      42             :   : DerivativeMaterialInterface<PorousFlowMaterial>(parameters),
      43        9871 :     _liquid_ph_num(getParam<unsigned>("liquid_phase")),
      44        9871 :     _liquid_phase(Moose::stringify(_liquid_ph_num)),
      45       19742 :     _initial_order(getParam<unsigned>("initial_order")),
      46       19742 :     _previous_turning_points(getParam<std::vector<Real>>("previous_turning_points")),
      47       19742 :     _sat_old(_nodal_material
      48        9871 :                  ? getMaterialPropertyOld<std::vector<Real>>("PorousFlow_saturation_nodal")
      49       26583 :                  : getMaterialPropertyOld<std::vector<Real>>("PorousFlow_saturation_qp")),
      50       19742 :     _sat_older(_nodal_material
      51        9871 :                    ? getMaterialPropertyOlder<std::vector<Real>>("PorousFlow_saturation_nodal")
      52       26583 :                    : getMaterialPropertyOlder<std::vector<Real>>("PorousFlow_saturation_qp")),
      53        9871 :     _hys_order(_nodal_material ? declareProperty<unsigned>("PorousFlow_hysteresis_order_nodal")
      54       18227 :                                : declareProperty<unsigned>("PorousFlow_hysteresis_order_qp")),
      55       19742 :     _hys_order_old(_nodal_material
      56        9871 :                        ? getMaterialPropertyOld<unsigned>("PorousFlow_hysteresis_order_nodal")
      57       26583 :                        : getMaterialPropertyOld<unsigned>("PorousFlow_hysteresis_order_qp")),
      58       19742 :     _hys_sat_tps(_nodal_material
      59        9871 :                      ? declareProperty<std::array<Real, PorousFlowConstants::MAX_HYSTERESIS_ORDER>>(
      60             :                            "PorousFlow_hysteresis_saturation_tps_nodal")
      61       18227 :                      : declareProperty<std::array<Real, PorousFlowConstants::MAX_HYSTERESIS_ORDER>>(
      62             :                            "PorousFlow_hysteresis_saturation_tps_qp")),
      63        9871 :     _hys_sat_tps_old(
      64        9871 :         _nodal_material
      65        9871 :             ? getMaterialPropertyOld<std::array<Real, PorousFlowConstants::MAX_HYSTERESIS_ORDER>>(
      66             :                   "PorousFlow_hysteresis_saturation_tps_nodal")
      67       26583 :             : getMaterialPropertyOld<std::array<Real, PorousFlowConstants::MAX_HYSTERESIS_ORDER>>(
      68        9871 :                   "PorousFlow_hysteresis_saturation_tps_qp"))
      69             : {
      70        9871 :   if (_liquid_ph_num >= _dictator.numPhases())
      71           2 :     paramError("liquid_phase",
      72             :                "The Dictator proclaims that the number of fluid phases is ",
      73           2 :                _dictator.numPhases(),
      74             :                " while you have foolishly entered ",
      75           2 :                _liquid_ph_num,
      76             :                " for your liquid phase number.  Remember that indexing starts at 0.  Be aware that "
      77             :                "the Dictator does not tolerate mistakes.");
      78        9869 :   if (_initial_order > PorousFlowConstants::MAX_HYSTERESIS_ORDER)
      79           2 :     paramError("initial_order",
      80             :                "The initial order must be less than the max order, which is hard-coded to ",
      81             :                PorousFlowConstants::MAX_HYSTERESIS_ORDER);
      82        9867 :   if (_previous_turning_points.size() != _initial_order)
      83           2 :     paramError("previous_turning_points",
      84             :                "initial_order is ",
      85             :                _initial_order,
      86             :                " so there must be this many previous_turning_points");
      87       21713 :   for (const auto & tp : _previous_turning_points)
      88       11852 :     if (tp < 0.0 || tp > 1.0)
      89           4 :       paramError("previous_turning_points",
      90             :                  "Each previous_turning_point must lie in the range [0.0, 1.0]");
      91             :   // check that the previous_turning_points are ordered correctly
      92             :   Real previous_tp = 1.0;
      93             :   Real previous_jump = std::numeric_limits<Real>::max();
      94             :   bool drying = true;
      95       21703 :   for (unsigned i = 0; i < _initial_order; ++i)
      96             :   {
      97       11848 :     const Real this_jump = _previous_turning_points[i] - previous_tp;
      98       11848 :     if (drying)
      99             :     {
     100        7948 :       if (this_jump >= 0)
     101           2 :         paramError("previous_turning_points",
     102             :                    "The previous_turning_points vector, {a, b, c, ...}, must be obey the following "
     103             :                    "order: a < 1; b > a; a < c < b, etc");
     104             :     }
     105             :     else
     106             :     {
     107        3900 :       if (this_jump <= 0)
     108           2 :         paramError("previous_turning_points",
     109             :                    "The previous_turning_points vector, {a, b, c, ...}, must be obey the following "
     110             :                    "order: a < 1; b > a; a < c < b, etc");
     111             :     }
     112       11844 :     if (std::abs(this_jump) >= previous_jump)
     113           2 :       paramError("previous_turning_points",
     114             :                  "The previous_turning_points vector, {a, b, c, ...}, must be obey the following "
     115             :                  "order: a < 1; b > a; a < c < b, etc");
     116       11842 :     drying = !drying;
     117             :     previous_tp = _previous_turning_points[i];
     118             :     previous_jump = std::abs(this_jump);
     119             :   }
     120        9855 : }
     121             : 
     122             : void
     123      345388 : PorousFlowHysteresisOrder::initQpStatefulProperties()
     124             : {
     125      345388 :   _hys_order[_qp] = _initial_order;
     126      778928 :   for (unsigned i = 0; i < _initial_order; ++i)
     127      433540 :     _hys_sat_tps[_qp].at(i) = _previous_turning_points[i];
     128      345388 : }
     129             : 
     130             : void
     131      955686 : PorousFlowHysteresisOrder::computeQpProperties()
     132             : {
     133             :   // NOTE to readers: the computed properties depend on old and older quantities only.  Hence, it
     134             :   // would be nice if we could compute them only at the start of each timestep instead of every
     135             :   // nonlinear iteration
     136             : 
     137             :   // For finite elements involved in boundary-condition calculations, number of nodes > number of
     138             :   // quadpoints.  Material values on these elements are not computed during initial setup, which
     139             :   // means that for timestep = 1, _sat_older.size() = number of quadpoints.  Since
     140             :   // computeQpProperties() is computed with 0 <= _qp < number of nodes (for nodal materials), this
     141             :   // results in an out-of-bounds error on _sat_older.  PorousFlow's implementation of hysteresis
     142             :   // lags one timestep behind, and assumes that _sat_older = _sat_old at timestep = 1 anyway, so
     143             :   // just use it:
     144             :   const MaterialProperty<std::vector<Real>> & sat_older =
     145      955686 :       (_nodal_material && _sat_old.size() != _sat_older.size()) ? _sat_old : _sat_older;
     146             : 
     147             :   // whether saturation has been reducing ("drying" or "draining"):
     148      955686 :   const bool drying = (_hys_order_old[_qp] % 2 == 0);
     149             :   // whether have been drying but are now increasing saturation ("wetting" or "imbibing"):
     150      955686 :   const bool dry2wet = drying && (_sat_old[_qp][_liquid_ph_num] > sat_older[_qp][_liquid_ph_num]);
     151             :   // whether have been wetting but are now decreasing saturation:
     152      955686 :   const bool wet2dry = !drying && (_sat_old[_qp][_liquid_ph_num] < sat_older[_qp][_liquid_ph_num]);
     153      955686 :   if ((dry2wet || wet2dry) && _hys_order_old[_qp] < PorousFlowConstants::MAX_HYSTERESIS_ORDER)
     154             :   {
     155       25410 :     _hys_order[_qp] = _hys_order_old[_qp] + 1;
     156       25410 :     _hys_sat_tps[_qp] = _hys_sat_tps_old[_qp];
     157       25410 :     _hys_sat_tps[_qp].at(_hys_order_old[_qp]) = sat_older[_qp][_liquid_ph_num];
     158             :   }
     159             :   else
     160             :   {
     161             :     // no change in wetting/drying direction
     162      930276 :     _hys_order[_qp] = _hys_order_old[_qp];
     163      930276 :     _hys_sat_tps[_qp] = _hys_sat_tps_old[_qp];
     164             :   }
     165      955686 :   if (_sat_old[_qp][_liquid_ph_num] >= 1.0)
     166       22358 :     _hys_order[_qp] = 0; // assume the system gets back to the original drying curve
     167             : 
     168             :   // reduce order by 1 if _hys_order = max and saturation exceeds bounds of last turning-point
     169      955686 :   if (_hys_order[_qp] == PorousFlowConstants::MAX_HYSTERESIS_ORDER)
     170             :   {
     171             :     // the maxiumum-order curve is a drying curve, and the saturation exceeds the previous turning
     172             :     // point:
     173             :     const bool drying_and_high_sat =
     174             :         (_hys_order[_qp] % 2 == 0) &&
     175             :         (_sat_old[_qp][_liquid_ph_num] >= _hys_sat_tps[_qp].at(_hys_order[_qp] - 1));
     176             :     // the maxiumum-order curve is a wetting curve, and the saturation is less than the previous
     177             :     // turning point:
     178             :     const bool wetting_and_low_sat =
     179      115532 :         (_hys_order[_qp] % 2 == 1) &&
     180      115532 :         (_sat_old[_qp][_liquid_ph_num] <= _hys_sat_tps[_qp].at(_hys_order[_qp] - 1));
     181      115532 :     if (drying_and_high_sat || wetting_and_low_sat)
     182        2256 :       _hys_order[_qp] -= 1;
     183             :   }
     184             : 
     185             :   // reduce order by 2 if saturation exceeds the bounds of the second-to-last turning-point
     186             :   // encountered
     187      955686 :   bool can_reduce_order = (_hys_order[_qp] > 1);
     188      964686 :   while (can_reduce_order)
     189             :   {
     190             :     can_reduce_order = false;
     191             :     // are drying and the saturation is <= a previously-encountered turning point:
     192             :     const bool drying_and_low_sat =
     193      296504 :         (_hys_order[_qp] % 2 == 0) &&
     194      183228 :         (_sat_old[_qp][_liquid_ph_num] <= _hys_sat_tps[_qp].at(_hys_order[_qp] - 2));
     195             :     // are wetting and the saturation is >= a previously-encountered turning point:
     196             :     const bool wetting_and_high_sat =
     197      296504 :         (_hys_order[_qp] % 2 == 1) &&
     198      113276 :         (_sat_old[_qp][_liquid_ph_num] >= _hys_sat_tps[_qp].at(_hys_order[_qp] - 2));
     199      296504 :     if (drying_and_low_sat || wetting_and_high_sat)
     200             :     {
     201        9000 :       _hys_order[_qp] -= 2;
     202        9000 :       can_reduce_order = (_hys_order[_qp] > 1);
     203             :     }
     204             :   }
     205      955686 : }

Generated by: LCOV version 1.14