void PorousFlowDarcyBase::upwind(JacRes res_or_jac, unsigned int jvar) { if ((res_or_jac == JacRes::CALCULATE_JACOBIAN) && _porousflow_dictator.notPorousFlowVariable(jvar)) return; /// The PorousFlow variable index corresponding to the variable number jvar const unsigned int pvar = ((res_or_jac == JacRes::CALCULATE_JACOBIAN) ? _porousflow_dictator.porousFlowVariableNum(jvar) : 0); /// The number of nodes in the element const unsigned int num_nodes = _test.size(); /// Compute the residual and jacobian without the mobility terms. Even if we are computing the Jacobian /// we still need this in order to see which nodes are upwind and which are downwind. std::vector<std::vector<Real>> component_re(num_nodes); for (_i = 0; _i < num_nodes; ++_i) { component_re[_i].assign(_num_phases, 0.0); for (_qp = 0; _qp < _qrule->n_points(); _qp++) for (unsigned ph = 0; ph < _num_phases; ++ph) component_re[_i][ph] += _JxW[_qp] * _coord[_qp] * darcyQp(ph); } DenseMatrix<Number> & ke = _assembly.jacobianBlock(_var.number(), jvar); if ((ke.n() == 0) && (res_or_jac == JacRes::CALCULATE_JACOBIAN)) // this removes a problem encountered in // the initial timestep when // use_displaced_mesh=true return; std::vector<std::vector<std::vector<Real>>> component_ke; if (res_or_jac == JacRes::CALCULATE_JACOBIAN) { component_ke.resize(ke.m()); for (_i = 0; _i < _test.size(); _i++) { component_ke[_i].resize(ke.n()); for (_j = 0; _j < _phi.size(); _j++) { component_ke[_i][_j].resize(_num_phases); for (_qp = 0; _qp < _qrule->n_points(); _qp++) for (unsigned ph = 0; ph < _num_phases; ++ph) component_ke[_i][_j][ph] += _JxW[_qp] * _coord[_qp] * darcyQpJacobian(jvar, ph); } } } /** * Now perform the upwinding by multiplying the residuals at the upstream nodes by their *mobilities. * Mobility is different for each phase, and in each situation: * mobility = density / viscosity for single-component Darcy flow * mobility = mass_fraction * density * relative_perm / viscosity for multi-component, *multiphase flow * mobility = enthalpy * density * relative_perm / viscosity for heat convection * * The residual for the kernel is the sum over Darcy fluxes for each phase. * The Darcy flux for a particular phase is * R_i = int{mobility*flux_no_mob} = int{mobility*grad(pot)*permeability*grad(test_i)} * for node i. where int is the integral over the element. * However, in fully-upwind, the first step is to take the mobility outside the integral, * which was done in the _component_re calculation above. * * NOTE: Physically _component_re[i][ph] is a measure of fluid of phase ph flowing out of node i. * If we had left in mobility, it would be exactly the component mass flux flowing out of node i. * * This leads to the definition of upwinding: * * If _component_re(i)[ph] is positive then we use mobility_i. That is we use the upwind value of *mobility. * * The final subtle thing is we must also conserve fluid mass: the total component mass flowing *out of node i * must be the sum of the masses flowing into the other nodes. **/ // Following are used below in steady-state calculations Real knorm = 0.0; for (unsigned int qp = 0; qp < _qrule->n_points(); ++qp) knorm += _permeability[qp].tr(); const Real lsq = _grad_test[0][0] * _grad_test[0][0]; const unsigned int dim = _mesh.dimension(); const Real l2md = std::pow(lsq, 0.5 * (2.0 - dim)); const Real l1md = std::pow(lsq, 0.5 * (1.0 - dim)); /// Loop over all the phases for (unsigned int ph = 0; ph < _num_phases; ++ph) { // FIRST: // this is a dirty way of getting around precision loss problems // and problems at steadystate where upwinding oscillates from // node-to-node causing nonconvergence. // The residual = int_{ele}*grad_test*k*(gradP - rho*g) = L^(d-1)*k*(gradP - rho*g), where d is // dimension // I assume that if one nodal P changes by P*1E-8 then this is // a "negligible" change. The residual will change by L^(d-2)*k*P*1E-8 // Similarly if rho*g changes by rho*g*1E-8 then this is a "negligible change" // Hence the formulae below, with grad_test = 1/L Real pnorm = 0.0; Real gravnorm = 0.0; for (unsigned int n = 0; n < num_nodes; ++n) { pnorm += _pp[n][ph] * _pp[n][ph]; gravnorm += _fluid_density_node[n][ph] * _fluid_density_node[n][ph]; } gravnorm *= _gravity * _gravity; const Real cutoff = 1.0E-8 * knorm * (std::sqrt(pnorm) * l2md + std::sqrt(gravnorm) * l1md); bool reached_steady = true; for (unsigned int nodenum = 0; nodenum < num_nodes; ++nodenum) { if (component_re[nodenum][ph] >= cutoff) { reached_steady = false; break; } } Real mob; Real dmob; /// Define variables used to ensure mass conservation Real total_mass_out = 0.0; Real total_in = 0.0; /// The following holds derivatives of these std::vector<Real> dtotal_mass_out; std::vector<Real> dtotal_in; if (res_or_jac == JacRes::CALCULATE_JACOBIAN) { dtotal_mass_out.resize(num_nodes); dtotal_in.resize(num_nodes); for (unsigned int n = 0; n < num_nodes; ++n) { dtotal_mass_out[n] = 0.0; dtotal_in[n] = 0.0; } } /// Perform the upwinding using the mobility std::vector<bool> upwind_node(num_nodes); for (unsigned int n = 0; n < num_nodes; ++n) { if (component_re[n][ph] >= cutoff || reached_steady) // upstream node { upwind_node[n] = true; /// The mobility at the upstream node mob = mobility(n, ph); if (res_or_jac == JacRes::CALCULATE_JACOBIAN) { /// The derivative of the mobility wrt the PorousFlow variable dmob = dmobility(n, ph, pvar); for (_j = 0; _j < _phi.size(); _j++) component_ke[n][_j][ph] *= mob; if (_test.size() == _phi.size()) /* mobility at node=n depends only on the variables at node=n, by construction. For * linear-lagrange variables, this means that Jacobian entries involving the derivative * of mobility will only be nonzero for derivatives wrt variables at node=n. Hence the * [n][n] in the line below. However, for other variable types (eg constant monomials) * I cannot tell what variable number contributes to the derivative. However, in all * cases I can possibly imagine, the derivative is zero anyway, since in the full * upwinding scheme, mobility shouldn't depend on these other sorts of variables. */ component_ke[n][n][ph] += dmob * component_re[n][ph]; for (_j = 0; _j < _phi.size(); _j++) dtotal_mass_out[_j] += component_ke[n][_j][ph]; } component_re[n][ph] *= mob; total_mass_out += component_re[n][ph]; } else { upwind_node[n] = false; total_in -= component_re[n][ph]; /// note the -= means the result is positive if (res_or_jac == JacRes::CALCULATE_JACOBIAN) for (_j = 0; _j < _phi.size(); _j++) dtotal_in[_j] -= component_ke[n][_j][ph]; } } /// Conserve mass over all phases by proportioning the total_mass_out mass to the inflow nodes, weighted by their component_re values if (!reached_steady) { for (unsigned int n = 0; n < num_nodes; ++n) { if (!upwind_node[n]) // downstream node { if (res_or_jac == JacRes::CALCULATE_JACOBIAN) for (_j = 0; _j < _phi.size(); _j++) { component_ke[n][_j][ph] *= total_mass_out / total_in; component_ke[n][_j][ph] += component_re[n][ph] * (dtotal_mass_out[_j] / total_in - dtotal_in[_j] * total_mass_out / total_in / total_in); } component_re[n][ph] *= total_mass_out / total_in; } } } } /// Add results to the Residual or Jacobian if (res_or_jac == JacRes::CALCULATE_RESIDUAL) { DenseVector<Number> & re = _assembly.residualBlock(_var.number()); _local_re.resize(re.size()); _local_re.zero(); for (_i = 0; _i < _test.size(); _i++) for (unsigned int ph = 0; ph < _num_phases; ++ph) _local_re(_i) += component_re[_i][ph]; re += _local_re; if (_has_save_in) { Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx); for (unsigned int i = 0; i < _save_in.size(); i++) _save_in[i]->sys().solution().add_vector(_local_re, _save_in[i]->dofIndices()); } } if (res_or_jac == JacRes::CALCULATE_JACOBIAN) { _local_ke.resize(ke.m(), ke.n()); _local_ke.zero(); for (_i = 0; _i < _test.size(); _i++) for (_j = 0; _j < _phi.size(); _j++) for (unsigned int ph = 0; ph < _num_phases; ++ph) _local_ke(_i, _j) += component_ke[_i][_j][ph]; ke += _local_ke; if (_has_diag_save_in && jvar == _var.number()) { unsigned int rows = ke.m(); DenseVector<Number> diag(rows); for (unsigned int i = 0; i < rows; i++) diag(i) = _local_ke(i, i); Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx); for (unsigned int i = 0; i < _diag_save_in.size(); i++) _diag_save_in[i]->sys().solution().add_vector(diag, _diag_save_in[i]->dofIndices()); } } }
void PorousFlowDarcyBase::fullyUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar) { /** * Perform the full upwinding by multiplying the residuals at the upstream nodes by their * mobilities. * Mobility is different for each phase, and in each situation: * mobility = density / viscosity for single-component Darcy flow * mobility = mass_fraction * density * relative_perm / viscosity for multi-component, *multiphase flow * mobility = enthalpy * density * relative_perm / viscosity for heat convection * * The residual for the kernel is the sum over Darcy fluxes for each phase. * The Darcy flux for a particular phase is * R_i = int{mobility*flux_no_mob} = int{mobility*grad(pot)*permeability*grad(test_i)} * for node i. where int is the integral over the element. * However, in fully-upwind, the first step is to take the mobility outside the integral, * which was done in the _proto_flux calculation above. * * NOTE: Physically _proto_flux[i][ph] is a measure of fluid of phase ph flowing out of node i. * If we had left in mobility, it would be exactly the component mass flux flowing out of node *i. * * This leads to the definition of upwinding: * * If _proto_flux(i)[ph] is positive then we use mobility_i. That is we use the upwind value of * mobility. * * The final subtle thing is we must also conserve fluid mass: the total component mass flowing * out of node i must be the sum of the masses flowing into the other nodes. **/ // The number of nodes in the element const unsigned int num_nodes = _test.size(); Real mob; Real dmob; // Define variables used to ensure mass conservation Real total_mass_out = 0.0; Real total_in = 0.0; // The following holds derivatives of these std::vector<Real> dtotal_mass_out; std::vector<Real> dtotal_in; if (res_or_jac == JacRes::CALCULATE_JACOBIAN) { dtotal_mass_out.assign(num_nodes, 0.0); dtotal_in.assign(num_nodes, 0.0); } // Perform the upwinding using the mobility std::vector<bool> upwind_node(num_nodes); for (unsigned int n = 0; n < num_nodes; ++n) { if (_proto_flux[ph][n] >= 0.0) // upstream node { upwind_node[n] = true; // The mobility at the upstream node mob = mobility(n, ph); if (res_or_jac == JacRes::CALCULATE_JACOBIAN) { // The derivative of the mobility wrt the PorousFlow variable dmob = dmobility(n, ph, pvar); for (_j = 0; _j < _phi.size(); _j++) _jacobian[ph][n][_j] *= mob; if (_test.size() == _phi.size()) /* mobility at node=n depends only on the variables at node=n, by construction. For * linear-lagrange variables, this means that Jacobian entries involving the derivative * of mobility will only be nonzero for derivatives wrt variables at node=n. Hence the * [n][n] in the line below. However, for other variable types (eg constant monomials) * I cannot tell what variable number contributes to the derivative. However, in all * cases I can possibly imagine, the derivative is zero anyway, since in the full * upwinding scheme, mobility shouldn't depend on these other sorts of variables. */ _jacobian[ph][n][n] += dmob * _proto_flux[ph][n]; for (_j = 0; _j < _phi.size(); _j++) dtotal_mass_out[_j] += _jacobian[ph][n][_j]; } _proto_flux[ph][n] *= mob; total_mass_out += _proto_flux[ph][n]; } else { upwind_node[n] = false; total_in -= _proto_flux[ph][n]; /// note the -= means the result is positive if (res_or_jac == JacRes::CALCULATE_JACOBIAN) for (_j = 0; _j < _phi.size(); _j++) dtotal_in[_j] -= _jacobian[ph][n][_j]; } } // Conserve mass over all phases by proportioning the total_mass_out mass to the inflow nodes, // weighted by their proto_flux values for (unsigned int n = 0; n < num_nodes; ++n) { if (!upwind_node[n]) // downstream node { if (res_or_jac == JacRes::CALCULATE_JACOBIAN) for (_j = 0; _j < _phi.size(); _j++) { _jacobian[ph][n][_j] *= total_mass_out / total_in; _jacobian[ph][n][_j] += _proto_flux[ph][n] * (dtotal_mass_out[_j] / total_in - dtotal_in[_j] * total_mass_out / total_in / total_in); } _proto_flux[ph][n] *= total_mass_out / total_in; } } }