LCOV - code coverage report
Current view: top level - src/dirackernels - PorousFlowPeacemanBorehole.C (source / functions) Hit Total Coverage
Test: idaholab/moose porous_flow: #31405 (292dce) with base fef103 Lines: 136 142 95.8 %
Date: 2025-09-04 07:55:56 Functions: 6 6 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 "PorousFlowPeacemanBorehole.h"
      11             : #include "RotationMatrix.h"
      12             : #include "Function.h"
      13             : 
      14             : registerMooseObject("PorousFlowApp", PorousFlowPeacemanBorehole);
      15             : 
      16             : InputParameters
      17        1076 : PorousFlowPeacemanBorehole::validParams()
      18             : {
      19        1076 :   InputParameters params = PorousFlowLineSink::validParams();
      20        2152 :   params.addRequiredParam<FunctionName>(
      21             :       "character",
      22             :       "If zero then borehole does nothing.  If positive the borehole acts as a sink "
      23             :       "(production well) for porepressure > borehole pressure, and does nothing "
      24             :       "otherwise.  If negative the borehole acts as a source (injection well) for "
      25             :       "porepressure < borehole pressure, and does nothing otherwise.  The flow rate "
      26             :       "to/from the borehole is multiplied by |character|, so usually character = +/- "
      27             :       "1, but you can specify other quantities to provide an overall scaling to the "
      28             :       "flow if you like.");
      29        2152 :   params.addRequiredParam<FunctionName>("bottom_p_or_t",
      30             :                                         "For function_of=pressure, this function is the "
      31             :                                         "pressure at the bottom of the borehole, "
      32             :                                         "otherwise it is the temperature at the bottom of "
      33             :                                         "the borehole.");
      34        2152 :   params.addRequiredParam<RealVectorValue>(
      35             :       "unit_weight",
      36             :       "(fluid_density*gravitational_acceleration) as a vector pointing downwards.  "
      37             :       "Note that the borehole pressure at a given z position is bottom_p_or_t + "
      38             :       "unit_weight*(q - q_bottom), where q=(x,y,z) and q_bottom=(x,y,z) of the "
      39             :       "bottom point of the borehole.  The analogous formula holds for "
      40             :       "function_of=temperature.  If you don't want bottomhole pressure (or "
      41             :       "temperature) to vary in the borehole just set unit_weight=0.  Typical value "
      42             :       "is = (0,0,-1E4), for water");
      43        2152 :   params.addParam<Real>("re_constant",
      44        2152 :                         0.28,
      45             :                         "The dimensionless constant used in evaluating the borehole effective "
      46             :                         "radius.  This depends on the meshing scheme.  Peacemann "
      47             :                         "finite-difference calculations give 0.28, while for rectangular finite "
      48             :                         "elements the result is closer to 0.1594.  (See  Eqn(4.13) of Z Chen, Y "
      49             :                         "Zhang, Well flow models for various numerical methods, Int J Num "
      50             :                         "Analysis and Modeling, 3 (2008) 375-388.)");
      51        2152 :   params.addParam<Real>("well_constant",
      52        2152 :                         -1.0,
      53             :                         "Usually this is calculated internally from the element geometry, the "
      54             :                         "local borehole direction and segment length, and the permeability.  "
      55             :                         "However, if this parameter is given as a positive number then this "
      56             :                         "number is used instead of the internal calculation.  This speeds up "
      57             :                         "computation marginally.  re_constant becomes irrelevant");
      58        1076 :   params.addClassDescription(
      59             :       "Approximates a borehole in the mesh using the Peaceman approach, ie "
      60             :       "using a number of point sinks with given radii whose positions are "
      61             :       "read from a file.  NOTE: if you are using PorousFlowPorosity that depends on volumetric "
      62             :       "strain, you should set strain_at_nearest_qp=true in your GlobalParams, to ensure the nodal "
      63             :       "Porosity Material uses the volumetric strain at the Dirac quadpoints, and can therefore be "
      64             :       "computed");
      65        1076 :   return params;
      66           0 : }
      67             : 
      68         610 : PorousFlowPeacemanBorehole::PorousFlowPeacemanBorehole(const InputParameters & parameters)
      69             :   : PorousFlowLineSink(parameters),
      70         588 :     _character(getFunction("character")),
      71         588 :     _p_bot(getFunction("bottom_p_or_t")),
      72        1176 :     _unit_weight(getParam<RealVectorValue>("unit_weight")),
      73        1176 :     _re_constant(getParam<Real>("re_constant")),
      74        1176 :     _well_constant(getParam<Real>("well_constant")),
      75         588 :     _has_permeability(
      76         588 :         hasMaterialProperty<RealTensorValue>("PorousFlow_permeability_qp") &&
      77        1174 :         hasMaterialProperty<std::vector<RealTensorValue>>("dPorousFlow_permeability_qp_dvar")),
      78         588 :     _has_thermal_conductivity(
      79         588 :         hasMaterialProperty<RealTensorValue>("PorousFlow_thermal_conductivity_qp") &&
      80         984 :         hasMaterialProperty<std::vector<RealTensorValue>>(
      81             :             "dPorousFlow_thermal_conductivity_qp_dvar")),
      82        1176 :     _perm_or_cond(_p_or_t == PorTchoice::pressure
      83         588 :                       ? getMaterialProperty<RealTensorValue>("PorousFlow_permeability_qp")
      84         724 :                       : getMaterialProperty<RealTensorValue>("PorousFlow_thermal_conductivity_qp")),
      85         588 :     _dperm_or_cond_dvar(
      86         588 :         _p_or_t == PorTchoice::pressure
      87        1176 :             ? getMaterialProperty<std::vector<RealTensorValue>>("dPorousFlow_permeability_qp_dvar")
      88         724 :             : getMaterialProperty<std::vector<RealTensorValue>>(
      89         610 :                   "dPorousFlow_thermal_conductivity_qp_dvar"))
      90             : {
      91         588 :   if (_p_or_t == PorTchoice::pressure && !_has_permeability)
      92           2 :     mooseError("PorousFlowPeacemanBorehole: You have specified function_of=porepressure, but you "
      93             :                "do not have a quadpoint permeability material");
      94         586 :   if (_p_or_t == PorTchoice::temperature && !_has_thermal_conductivity)
      95           2 :     mooseError("PorousFlowPeacemanBorehole: You have specified function_of=temperature, but you do "
      96             :                "not have a quadpoint thermal_conductivity material");
      97         584 : }
      98             : 
      99             : void
     100         581 : PorousFlowPeacemanBorehole::initialSetup()
     101             : {
     102         581 :   PorousFlowLineGeometry::initialSetup();
     103             : 
     104         577 :   if (!_point_file.empty() && _zs[0] < _zs.back())
     105           2 :     mooseError("PorousFlowPeacemanBorehole: The last entry in the point_file needs to be at the "
     106             :                "bottom of the well_bore because this is the point where the function bottom_p_or_t "
     107             :                "is evaluated.  The depth of the first point is z=",
     108             :                _zs[0],
     109             :                " and the last point is z=",
     110             :                _zs.back());
     111             : 
     112             :   // construct the rotation matrix needed to rotate the permeability
     113         575 :   const unsigned int num_pts = _zs.size();
     114         839 :   _rot_matrix.resize(std::max(num_pts - 1, (unsigned)1));
     115        1062 :   for (unsigned int i = 0; i + 1 < num_pts; ++i)
     116             :   {
     117         487 :     const RealVectorValue v2(_xs[i + 1] - _xs[i], _ys[i + 1] - _ys[i], _zs[i + 1] - _zs[i]);
     118         487 :     _rot_matrix[i] = RotationMatrix::rotVecToZ(v2);
     119             :   }
     120         575 :   if (num_pts == (unsigned)1)
     121         264 :     _rot_matrix[0] = RotationMatrix::rotVecToZ(_line_direction);
     122         575 : }
     123             : 
     124             : Real
     125     1012258 : PorousFlowPeacemanBorehole::wellConstant(const RealTensorValue & perm,
     126             :                                          const RealTensorValue & rot,
     127             :                                          const Real & half_len,
     128             :                                          const Elem * ele,
     129             :                                          const Real & rad) const
     130             : // Peaceman's form for the borehole well constant
     131             : {
     132     1012258 :   if (_well_constant > 0)
     133             :     return _well_constant;
     134             : 
     135             :   // rot_perm has its "2" component lying along the half segment.
     136             :   // We want to determine the eigenvectors of rot(0:1, 0:1), since, when
     137             :   // rotated back to the original frame we will determine the element
     138             :   // lengths along these directions
     139     1012258 :   const RealTensorValue rot_perm = (rot * perm) * rot.transpose();
     140     1012258 :   const Real trace2D = rot_perm(0, 0) + rot_perm(1, 1);
     141     1012258 :   const Real det2D = rot_perm(0, 0) * rot_perm(1, 1) - rot_perm(0, 1) * rot_perm(1, 0);
     142     2024516 :   const Real sq = std::sqrt(std::max(0.25 * trace2D * trace2D - det2D,
     143     1012258 :                                      0.0)); // the std::max accounts for wierdo precision loss
     144     1012258 :   const Real eig_val1 = 0.5 * trace2D + sq;
     145     1012258 :   const Real eig_val2 = 0.5 * trace2D - sq;
     146             :   RealVectorValue eig_vec1, eig_vec2;
     147     1012258 :   if (sq > std::abs(trace2D) * 1E-7) // matrix is not a multiple of the identity (1E-7 accounts for
     148             :                                      // precision in a crude way)
     149             :   {
     150      148360 :     if (rot_perm(1, 0) != 0)
     151             :     {
     152      120640 :       eig_vec1(0) = eig_val1 - rot_perm(1, 1);
     153      120640 :       eig_vec1(1) = rot_perm(1, 0);
     154      120640 :       eig_vec2(0) = eig_val2 - rot_perm(1, 1);
     155      120640 :       eig_vec2(1) = rot_perm(1, 0);
     156             :     }
     157       27720 :     else if (rot_perm(0, 1) != 0)
     158             :     {
     159           0 :       eig_vec1(0) = rot_perm(0, 1);
     160           0 :       eig_vec1(1) = eig_val1 - rot_perm(0, 0);
     161           0 :       eig_vec2(0) = rot_perm(0, 1);
     162           0 :       eig_vec2(1) = eig_val2 - rot_perm(0, 0);
     163             :     }
     164             :     else // off diagonal terms are both zero
     165             :     {
     166       27720 :       eig_vec1(0) = 1.0;
     167       27720 :       eig_vec2(1) = 1.0;
     168             :     }
     169             :   }
     170             :   else // matrix is basically a multiple of the identity
     171             :   {
     172      863898 :     eig_vec1(0) = 1.0;
     173      863898 :     eig_vec2(1) = 1.0;
     174             :   }
     175             : 
     176             :   // finally, rotate these to original frame and normalise
     177     1012258 :   eig_vec1 = rot.transpose() * eig_vec1;
     178     1012258 :   eig_vec1 /= std::sqrt(eig_vec1 * eig_vec1);
     179     1012258 :   eig_vec2 = rot.transpose() * eig_vec2;
     180     1012258 :   eig_vec2 /= std::sqrt(eig_vec2 * eig_vec2);
     181             : 
     182             :   // find the "length" of the element in these directions
     183             :   // TODO - maybe better to use variance than max&min
     184             :   Real max1 = eig_vec1 * ele->point(0);
     185             :   Real max2 = eig_vec2 * ele->point(0);
     186             :   Real min1 = max1;
     187             :   Real min2 = max2;
     188             :   Real proj;
     189     8098064 :   for (unsigned int i = 1; i < ele->n_nodes(); i++)
     190             :   {
     191             :     proj = eig_vec1 * ele->point(i);
     192     7085806 :     max1 = (max1 < proj) ? proj : max1;
     193     7085806 :     min1 = (min1 < proj) ? min1 : proj;
     194             : 
     195             :     proj = eig_vec2 * ele->point(i);
     196     7085806 :     max2 = (max2 < proj) ? proj : max2;
     197     7085806 :     min2 = (min2 < proj) ? min2 : proj;
     198             :   }
     199     1012258 :   const Real ll1 = max1 - min1;
     200     1012258 :   const Real ll2 = max2 - min2;
     201             : 
     202             :   Real r0;
     203     1012258 :   if (eig_val1 <= 0.0)
     204           0 :     r0 = _re_constant * ll1;
     205     1012258 :   else if (eig_val2 <= 0.0)
     206       18560 :     r0 = _re_constant * ll2;
     207             :   else
     208      993698 :     r0 = _re_constant *
     209      993698 :          std::sqrt(std::sqrt(eig_val1 / eig_val2) * std::pow(ll2, 2) +
     210      993698 :                    std::sqrt(eig_val2 / eig_val1) * std::pow(ll1, 2)) /
     211      993698 :          (std::pow(eig_val1 / eig_val2, 0.25) + std::pow(eig_val2 / eig_val1, 0.25));
     212             : 
     213     1012258 :   const Real effective_perm = (det2D >= 0.0 ? std::sqrt(det2D) : 0.0);
     214             : 
     215             :   const Real halfPi = acos(0.0);
     216             : 
     217     1012258 :   if (r0 <= rad)
     218           2 :     mooseError("The effective element size (about 0.2-times-true-ele-size) for an element "
     219             :                "containing a Peaceman-type borehole must be (much) larger than the borehole radius "
     220             :                "for the Peaceman formulation to be correct.  Your element has effective size ",
     221             :                r0,
     222             :                " and the borehole radius is ",
     223             :                rad,
     224             :                "\n");
     225             : 
     226     1012256 :   return 4 * halfPi * effective_perm * half_len / std::log(r0 / rad);
     227             : }
     228             : 
     229             : Real
     230      222018 : PorousFlowPeacemanBorehole::computeQpBaseOutflow(unsigned current_dirac_ptid) const
     231             : {
     232      222018 :   const Real character = _character.value(_t, _q_point[_qp]);
     233      222018 :   if (character == 0.0)
     234             :     return 0.0;
     235             : 
     236             :   const Real bh_pressure =
     237      186018 :       _p_bot.value(_t, _bottom_point) + _unit_weight * (_q_point[_qp] - _bottom_point);
     238      186018 :   const Real pp = ptqp();
     239             : 
     240             :   Real outflow = 0.0; // this is the flow rate from porespace out of the system
     241             : 
     242      186018 :   if (current_dirac_ptid > 0)
     243             :   // contribution from half-segment "behind" this point (must have >1 point for
     244             :   // current_dirac_ptid>0)
     245             :   {
     246       77288 :     if ((character < 0.0 && pp < bh_pressure) || (character > 0.0 && pp > bh_pressure))
     247             :     {
     248             :       // injection, so outflow<0 || production, so outflow>0
     249       77144 :       const Real wc = wellConstant(_perm_or_cond[_qp],
     250             :                                    _rot_matrix[current_dirac_ptid - 1],
     251       77144 :                                    _half_seg_len[current_dirac_ptid - 1],
     252       77144 :                                    _current_elem,
     253       77144 :                                    _weight->at(current_dirac_ptid));
     254       77144 :       outflow += wc * (pp - bh_pressure);
     255             :     }
     256             :   }
     257             : 
     258      186018 :   if (current_dirac_ptid + 1 < _zs.size() || _zs.size() == 1)
     259             :   // contribution from half-segment "ahead of" this point, or we only have one point
     260             :   {
     261      123130 :     if ((character < 0.0 && pp < bh_pressure) || (character > 0.0 && pp > bh_pressure))
     262             :     {
     263             :       // injection, so outflow<0 || // production, so outflow>0
     264      122506 :       const Real wc = wellConstant(_perm_or_cond[_qp],
     265             :                                    _rot_matrix[current_dirac_ptid],
     266             :                                    _half_seg_len[current_dirac_ptid],
     267      122506 :                                    _current_elem,
     268      122506 :                                    _weight->at(current_dirac_ptid));
     269      122504 :       outflow += wc * (pp - bh_pressure);
     270             :     }
     271             :   }
     272             : 
     273      186016 :   return outflow * _test[_i][_qp] * std::abs(character);
     274             : }
     275             : 
     276             : void
     277      838456 : PorousFlowPeacemanBorehole::computeQpBaseOutflowJacobian(unsigned jvar,
     278             :                                                          unsigned current_dirac_ptid,
     279             :                                                          Real & outflow,
     280             :                                                          Real & outflowp) const
     281             : {
     282      838456 :   outflow = 0.0;
     283      838456 :   outflowp = 0.0;
     284             : 
     285      838456 :   const Real character = _character.value(_t, _q_point[_qp]);
     286      838456 :   if (character == 0.0)
     287             :     return;
     288             : 
     289      804856 :   if (_dictator.notPorousFlowVariable(jvar))
     290             :     return;
     291      804856 :   const unsigned pvar = _dictator.porousFlowVariableNum(jvar);
     292             : 
     293             :   const Real bh_pressure =
     294      804856 :       _p_bot.value(_t, _bottom_point) + _unit_weight * (_q_point[_qp] - _bottom_point);
     295      804856 :   const Real pp = ptqp();
     296      804856 :   const Real pp_prime = dptqp(pvar) * _phi[_j][_qp];
     297             : 
     298      804856 :   if (current_dirac_ptid > 0)
     299             :   // contribution from half-segment "behind" this point
     300             :   {
     301      387328 :     if ((character < 0.0 && pp < bh_pressure) || (character > 0.0 && pp > bh_pressure))
     302             :     {
     303             :       // injection, so outflow<0 || // production, so outflow>0
     304      385024 :       const Real wc = wellConstant(_perm_or_cond[_qp],
     305             :                                    _rot_matrix[current_dirac_ptid - 1],
     306      385024 :                                    _half_seg_len[current_dirac_ptid - 1],
     307      385024 :                                    _current_elem,
     308      385024 :                                    _weight->at(current_dirac_ptid));
     309      385024 :       outflowp += wc * pp_prime;
     310      385024 :       outflow += wc * (pp - bh_pressure);
     311             :     }
     312             :   }
     313             : 
     314      804856 :   if (current_dirac_ptid < _zs.size() - 1 || _zs.size() == 1)
     315             :   // contribution from half-segment "ahead of" this point
     316             :   {
     317      430968 :     if ((character < 0.0 && pp < bh_pressure) || (character > 0.0 && pp > bh_pressure))
     318             :     {
     319             :       // injection, so outflow<0 || // production, so outflow>0
     320      427584 :       const Real wc = wellConstant(_perm_or_cond[_qp],
     321             :                                    _rot_matrix[current_dirac_ptid],
     322             :                                    _half_seg_len[current_dirac_ptid],
     323      427584 :                                    _current_elem,
     324      427584 :                                    _weight->at(current_dirac_ptid));
     325      427584 :       outflowp += wc * pp_prime;
     326      427584 :       outflow += wc * (pp - bh_pressure);
     327             :     }
     328             :   }
     329             : 
     330      804856 :   outflowp *= _test[_i][_qp] * std::abs(character);
     331      804856 :   outflow *= _test[_i][_qp] * std::abs(character);
     332             : }

Generated by: LCOV version 1.14