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