OGS
WellboreSimulatorFEM-impl.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#pragma once
5
6#include <Eigen/Dense>
7#include <vector>
8
15
16namespace ProcessLib
17{
18namespace WellboreSimulator
19{
20template <typename ShapeFunction, int GlobalDim>
22 double const t, double const dt, std::vector<double> const& local_x,
23 std::vector<double> const& local_x_prev, std::vector<double>& local_M_data,
24 std::vector<double>& local_K_data, std::vector<double>& local_b_data)
25{
26 auto const local_matrix_size = local_x.size();
27
28 assert(local_matrix_size == ShapeFunction::NPOINTS * NUM_NODAL_DOF);
29
31 local_M_data, local_matrix_size, local_matrix_size);
33 local_K_data, local_matrix_size, local_matrix_size);
35 local_b_data, local_matrix_size);
36
37 // Get block matrices
38 auto Mvv = local_M.template block<velocity_size, velocity_size>(
40
41 auto Mhp = local_M.template block<enthalpy_size, pressure_size>(
43 auto Mhh = local_M.template block<enthalpy_size, enthalpy_size>(
45
46 auto Kpv = local_K.template block<pressure_size, velocity_size>(
48
49 auto Kvp = local_K.template block<velocity_size, pressure_size>(
51 auto Kvv = local_K.template block<velocity_size, velocity_size>(
53
54 auto Khh = local_K.template block<enthalpy_size, enthalpy_size>(
56
57 auto Bp = local_b.template segment<pressure_size>(pressure_index);
58 auto Bv = local_b.template segment<velocity_size>(velocity_index);
59 auto Bh = local_b.template segment<enthalpy_size>(enthalpy_index);
60
61 unsigned const n_integration_points =
62 _integration_method.getNumberOfPoints();
63
65 pos.setElementID(_element.getID());
66
67 auto const& b = _process_data.specific_body_force;
68
70
71 // Get material properties
72 auto const& medium = *_process_data.media_map.getMedium(_element.getID());
73 auto const& liquid_phase = medium.phase("AqueousLiquid");
74 auto const& gas_phase = medium.phase("Gas");
75
76 // Get wellbore parameters
77 // casing thickness
78 auto const t_ca = _process_data.wellbore.casing_thickness(t, pos)[0];
79 // wellbore radius
80 auto const r_w = _process_data.wellbore.diameter(t, pos)[0] / 2;
81
82 // pipe thickness
83 auto const t_p = _process_data.wellbore.pipe_thickness(t, pos)[0];
84
85 // roughness of the wellbore
86 auto const xi = _process_data.wellbore.roughness(t, pos)[0];
87 // pipe outer radius
88 auto const r_o = r_w - t_ca;
89 // pipe inner radius
90 auto const r_i = r_o - t_p;
91
92 // get reservoir properties
93 NodalVectorType T_r =
94 _process_data.reservoir_properties.temperature.getNodalValuesOnElement(
95 _element, t);
96 NodalVectorType p_r =
97 _process_data.reservoir_properties.pressure.getNodalValuesOnElement(
98 _element, t);
100 _process_data.productivity_index.getNodalValuesOnElement(_element, t);
101 auto const k_r =
102 _process_data.reservoir_properties.thermal_conductivity(t, pos)[0];
103 auto const rho_r = _process_data.reservoir_properties.density(t, pos)[0];
104 auto const c_r =
105 _process_data.reservoir_properties.specific_heat_capacity(t, pos)[0];
106
107 for (unsigned ip(0); ip < n_integration_points; ip++)
108 {
109 auto& ip_data = _ip_data[ip];
110 auto const& N = ip_data.N;
111 auto const& dNdx = ip_data.dNdx;
112 auto const& w = ip_data.integration_weight;
113 auto& mix_density = ip_data.mix_density;
114 auto& temperature = ip_data.temperature;
115 auto& steam_mass_frac = ip_data.dryness;
116 auto& vapor_volume_frac = ip_data.vapor_volume_fraction;
117 auto& vapor_mass_flowrate = ip_data.vapor_mass_flow_rate;
118 auto& liquid_mass_flowrate = ip_data.liquid_mass_flow_rate;
119
120 pos = {
121 std::nullopt, _element.getID(),
124 _element, N))};
125
126 double p_int_pt = 0.0;
127 double v_int_pt = 0.0;
128 double h_int_pt = 0.0;
129
130 NumLib::shapeFunctionInterpolate(local_x, N, p_int_pt, v_int_pt,
131 h_int_pt);
132
133 double p_prev_int_pt = 0.0;
134 double v_prev_int_pt = 0.0;
135 double h_prev_int_pt = 0.0;
136
137 NumLib::shapeFunctionInterpolate(local_x_prev, N, p_prev_int_pt,
138 v_prev_int_pt, h_prev_int_pt);
139
140 double vdot_int_pt = (v_int_pt - v_prev_int_pt) / dt;
141
142 // calculate fluid properties
143
144 const double pi = std::numbers::pi;
145
146 vars.liquid_phase_pressure = p_int_pt;
147 vars.enthalpy = h_int_pt;
148
149 double liquid_water_density =
150 liquid_phase
152 .template value<double>(vars, pos, t, dt);
153 double const vapour_water_density =
154 gas_phase
156 .template value<double>(vars, pos, t, dt);
157
158 double const h_sat_liq_w =
159 liquid_phase
160 .property(
162 .template value<double>(vars, pos, t, dt);
163 double const h_sat_vap_w =
164 gas_phase
165 .property(
167 .template value<double>(vars, pos, t, dt);
168
169 // TODO add a function to calculate dryness with constrain of 0
170 // to 1.
171 double const dryness = std::max(
172 0., (h_int_pt - h_sat_liq_w) / (h_sat_vap_w - h_sat_liq_w));
173 steam_mass_frac = dryness;
174
175 double const T_int_pt =
176 (dryness == 0)
177 ? liquid_phase
179 .template value<double>(vars, pos, t, dt)
180 : gas_phase
182 saturation_temperature)
183 .template value<double>(vars, pos, t, dt);
184 temperature = T_int_pt;
185 vars.temperature = T_int_pt;
186
187 // For the calculation of the void fraction of vapour,
188 // see Rohuani, Z., and E. Axelsson. "Calculation of volume void
189 // fraction in a subcooled and quality region." International
190 // Journal of Heat and Mass Transfer 17 (1970): 383-393.
191
192 // profile parameter of drift flux
193 double C_0 = 1 + 0.12 * (1 - dryness);
194
195 // For the surface tension calculation, see
196 // Cooper, J. R., and R. B. Dooley. "IAPWS release on surface
197 // tension of ordinary water substance." International Association
198 // for the Properties of Water and Steam (1994).
199 double const sigma_gl = 0.2358 *
200 std::pow((1 - T_int_pt / 647.096), 1.256) *
201 (1 - 0.625 * (1 - T_int_pt / 647.096));
202 // drift flux velocity
203 double const u_gu =
204 1.18 * (1 - dryness) *
205 std::pow((9.81) * sigma_gl *
206 (liquid_water_density - vapour_water_density),
207 0.25) /
208 std::pow(liquid_water_density, 0.5);
209
210 // solving void fraction of vapor: Rouhani-Axelsson
211 double alpha = 0;
212 if (dryness != 0)
213 {
214 // Local Newton solver
215 using LocalJacobianMatrix =
216 Eigen::Matrix<double, 1, 1, Eigen::RowMajor>;
217 using LocalResidualVector = Eigen::Matrix<double, 1, 1>;
218 using LocalUnknownVector = Eigen::Matrix<double, 1, 1>;
219 LocalJacobianMatrix J_loc;
220
221 Eigen::PartialPivLU<LocalJacobianMatrix> linear_solver(1);
222
223 auto const update_residual = [&](LocalResidualVector& residual)
224 {
225 calculateResidual(alpha, vapour_water_density,
226 liquid_water_density, v_int_pt, dryness, C_0,
227 u_gu, residual);
228 };
229
230 auto const update_jacobian = [&](LocalJacobianMatrix& jacobian)
231 {
233 alpha, vapour_water_density, liquid_water_density, v_int_pt,
234 dryness, C_0, u_gu,
235 jacobian); // for solution dependent Jacobians
236 };
237
238 auto const update_solution =
239 [&](LocalUnknownVector const& increment)
240 {
241 // increment solution vectors
242 alpha += increment[0];
243 };
244
245 const int maximum_iterations(20);
246 const double residuum_tolerance(1.e-10);
247 const double increment_tolerance(0);
248
249 auto newton_solver = NumLib::NewtonRaphson(
250 linear_solver, update_jacobian, update_residual,
251 update_solution,
252 {maximum_iterations, residuum_tolerance, increment_tolerance});
253
254 auto const success_iterations = newton_solver.solve(J_loc);
255
256 if (!success_iterations)
257 {
258 WARN(
259 "Attention! Steam void fraction has not been correctly "
260 "calculated!");
261 }
262 }
263
264 vapor_volume_frac = alpha;
265
266 if (alpha == 0)
267 {
268 liquid_water_density =
269 liquid_phase
271 .template value<double>(vars, pos, t, dt);
272 }
273
274 mix_density =
275 vapour_water_density * alpha + liquid_water_density * (1 - alpha);
276
277 auto& mix_density_prev = ip_data.mix_density_prev;
278 vars.density = mix_density;
279
280 auto const rho_dot = (mix_density - mix_density_prev) / dt;
281
282 double const liquid_water_velocity_act =
283 (alpha == 0) ? v_int_pt
284 : (1 - dryness) * mix_density * v_int_pt /
285 (1 - alpha) / liquid_water_density;
286 double const vapor_water_velocity_act =
287 (alpha == 0) ? 0
288 : dryness * mix_density * v_int_pt /
289 (alpha * vapour_water_density);
290
291 vapor_mass_flowrate = vapor_water_velocity_act * vapour_water_density *
292 pi * r_i * r_i * alpha;
293
294 liquid_mass_flowrate = liquid_water_velocity_act *
295 liquid_water_density * pi * r_i * r_i *
296 (1 - alpha);
297
298 // Slip parameter between two phases,
299 // see Akbar, Somaieh, N. Fathianpour, and Rafid Al Khoury. "A finite
300 // element model for high enthalpy two-phase flow in geothermal
301 // wellbores." Renewable Energy 94 (2016): 223-236.
302 double const gamma =
303 alpha * liquid_water_density * vapour_water_density * mix_density /
304 (1 - alpha) /
305 std::pow((alpha * C_0 * vapour_water_density +
306 (1 - alpha * C_0) * liquid_water_density),
307 2) *
308 std::pow((C_0 - 1) * v_int_pt + u_gu, 2);
309
310 double const miu =
312 .template value<double>(vars, pos, t, dt);
313 double const Re = mix_density * v_int_pt * 2 * r_i / miu;
314
315 // Wall friction coefficient,
316 // Musivand Arzanfudi, Mehdi, and Rafid Al‐Khoury. "A compressible
317 // two‐fluid multiphase model for CO2 leakage through a wellbore."
318 // International Journal for Numerical Methods in Fluids 77.8 (2015):
319 // 477-507.
320 double f = 0.0;
321 if (Re > 10 && Re <= 2400)
322 f = 16 / Re;
323 else if (Re > 2400)
324 f = std::pow(std::log(xi / 3.7 / r_i) -
325 5.02 / Re * std::log(xi / 3.7 / r_i + 13 / Re),
326 -2) /
327 16;
328
329 double Q_hx = 0;
330 double const T_r_int_pt = N.dot(T_r);
331 // conductive heat exchange between wellbore and formation
332 if (_process_data.has_heat_exchange_with_formation)
333 {
334 // See Zhang, Pan, Pruess, Finsterle (2011). A time-convolution
335 // approach for modeling heat exchange between a wellbore and
336 // surrounding formation. Geothermics 40, 261-266.
337 const double alpha_r = k_r / rho_r / c_r;
338 const double t_d = alpha_r * t / (r_i * r_i);
339
340 double beta;
341 if (t_d < 2.8)
342 beta = std::pow((pi * t_d), -0.5) + 0.5 -
343 0.25 * std::pow((t_d / pi), 0.5) + 0.125 * t_d;
344 else
345 beta = 2 * (1 / (std::log(4 * t_d) - 2 * 0.57722) -
346 0.57722 /
347 std::pow((std::log(4 * t_d) - 2 * 0.57722), 2));
348
349 const double P_c = 2 * pi * r_i;
350 Q_hx = P_c * k_r * (T_r_int_pt - T_int_pt) / r_i * beta;
351 }
352
353 // mass exchange with reservoir
354 double const p_r_int_pt = N.dot(p_r);
355 double const PI_int_pt = N.dot(PI);
356 double Q_mx = PI_int_pt * (p_int_pt - p_r_int_pt);
357
358 // advective momentum and energy exchange with reservoir due to the mass
359 // exchange
360 double Q_mom = 0;
361 double Q_ene = 0;
362 if (Q_mx != 0)
363 {
364 Q_mom = Q_mx * v_int_pt;
365 // Only single-phase liquid condition is considered now
366 // TODO: update the two-phase flowing enthalpy from the feed zone.
367 vars.liquid_phase_pressure = p_r_int_pt;
368 vars.temperature = T_r_int_pt;
369 double const h_fres =
370 liquid_phase
372 .template value<double>(vars, pos, t, dt);
373 Q_ene = Q_mx * h_fres;
374 }
375
376 // M matrix assembly
377 Mvv.noalias() += w * N.transpose() * mix_density * N;
378
379 Mhp.noalias() += -w * N.transpose() * N;
380 Mhh.noalias() += w * N.transpose() * mix_density * N;
381
382 // K matrix assembly
383 Kpv.noalias() += w * dNdx.transpose() * N * mix_density;
384
385 Kvp.noalias() += w * N.transpose() * dNdx;
386 Kvv.noalias() += w * N.transpose() * rho_dot * N;
387
388 Khh.noalias() += w * N.transpose() * mix_density * v_int_pt * dNdx;
389
390 // b matrix assembly
391 Bp.noalias() += w * N.transpose() * rho_dot + w * N.transpose() * Q_mx;
392
393 Bv.noalias() +=
394 w * dNdx.transpose() * mix_density * v_int_pt * v_int_pt +
395 w * dNdx.transpose() * gamma -
396 w * N.transpose() * f * mix_density * std::abs(v_int_pt) *
397 v_int_pt / (4 * r_i) -
398 w * N.transpose() * Q_mom;
399
400 Bh.noalias() +=
401 -1 / 2 * w * N.transpose() * rho_dot * v_int_pt * v_int_pt -
402 w * N.transpose() * mix_density * v_int_pt * vdot_int_pt +
403 1 / 2 * w * dNdx.transpose() * mix_density * v_int_pt * v_int_pt *
404 v_int_pt +
405 w * N.transpose() * (Q_hx / pi / r_i / r_i) -
406 w * N.transpose() * Q_ene;
407
408 if (_process_data.has_gravity)
409 {
410 NodalVectorType gravity_operator =
411 N.transpose() * b * w * _element_direction[2];
412
413 Bv.noalias() += gravity_operator * mix_density;
414 Bh.noalias() += gravity_operator * mix_density * v_int_pt;
415 }
416 }
417
418 // debugging
419 // std::string sep = "\n----------------------------------------\n";
420 // Eigen::IOFormat CleanFmt(6, 0, ", ", "\n", "[", "]");
421 // std::cout << local_M.format(CleanFmt) << sep;
422 // std::cout << local_K.format(CleanFmt) << sep;
423 // std::cout << local_b.format(CleanFmt) << sep;
424}
425
426} // namespace WellboreSimulator
427} // namespace ProcessLib
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:34
const double PI
Definition QArrow.h:9
std::optional< int > solve(JacobianMatrix &jacobian) const
void setElementID(std::size_t element_id)
typename ShapeMatricesType::NodalVectorType NodalVectorType
void calculateJacobian(double const alpha, double const vapor_water_density, double const liquid_water_density, double const v_mix, double const dryness, double const C_0, double const u_gu, JacobianMatrix &Jac)
NumLib::GenericIntegrationMethod const & _integration_method
ShapeMatrixPolicyType< ShapeFunction, GlobalDim > ShapeMatricesType
void assemble(double const t, double const dt, std::vector< double > const &local_x, std::vector< double > const &local_x_prev, std::vector< double > &local_M_data, std::vector< double > &local_K_data, std::vector< double > &local_b_data) override
void calculateResidual(double const alpha, double const vapor_water_density, double const liquid_water_density, double const v_mix, double const dryness, double const C_0, double const u_gu, ResidualVector &res)
std::vector< IpData, Eigen::aligned_allocator< IpData > > _ip_data
Eigen::Map< Vector > createZeroedVector(std::vector< double > &data, Eigen::VectorXd::Index size)
Eigen::Map< Matrix > createZeroedMatrix(std::vector< double > &data, Eigen::MatrixXd::Index rows, Eigen::MatrixXd::Index cols)
void shapeFunctionInterpolate(const NodalValues &, const ShapeMatrix &)
std::array< double, 3 > interpolateCoordinates(MeshLib::Element const &e, typename ShapeMatricesType::ShapeMatrices::ShapeType const &N)