Real RichardsFlux::computeQpJacobian() { Real mob = mobility(_density[_qp][_pvar], _rel_perm[_qp][_pvar]); Real dmob_dp = dmobility_dp(_density[_qp][_pvar], _ddensity[_qp][_pvar], _rel_perm[_qp][_pvar], _drel_perm[_qp][_pvar]*_dseff[_qp][_pvar][_pvar]); RealVectorValue pot = _permeability[_qp]*(_grad_u[_qp] - _density[_qp][_pvar]*_gravity[_qp]); RealVectorValue dpot_dp = _permeability[_qp]*(_grad_phi[_j][_qp] - _phi[_j][_qp]*_ddensity[_qp][_pvar]*_gravity[_qp]); // note: includes _phi Real dflux_dp = _grad_test[_i][_qp]*(dmob_dp*_phi[_j][_qp]*pot + mob*dpot_dp); Real supg_test = _tauvel_SUPG[_qp][_pvar]*_grad_test[_i][_qp]; Real supg_test_prime = _grad_phi[_j][_qp]*(_dtauvel_SUPG_dgradp[_qp][_pvar]*_grad_test[_i][_qp]) + _phi[_j][_qp]*_dtauvel_SUPG_dp[_qp][_pvar]*_grad_test[_i][_qp]; Real supg_kernel = 0.0; Real supg_kernel_prime = 0.0; if (supg_test != 0) { RealVectorValue grad_mob = dmob_dp*_grad_u[_qp]; // NOTE: since Libmesh does not correctly calculate grad(_grad_u) correctly, so following might not be correct Real div_pot = ((_permeability[_qp]*_second_u[_qp]).tr() - (_permeability[_qp]*_grad_u[_qp])*_ddensity[_qp][_pvar]*_gravity[_qp]); supg_kernel = -grad_mob*pot - mob*div_pot; Real d2mob_dp2 = d2mobility_dp2(_density[_qp][_pvar], _ddensity[_qp][_pvar], _d2density[_qp][_pvar], _rel_perm[_qp][_pvar], _drel_perm[_qp][_pvar]*_dseff[_qp][_pvar][_pvar], _d2rel_perm[_qp][_pvar]*_dseff[_qp][_pvar][_pvar]*_dseff[_qp][_pvar][_pvar] + _drel_perm[_qp][_pvar]*_d2seff[_qp][_pvar][_pvar][_pvar]); RealVectorValue dgrad_mob_dp = d2mob_dp2*_phi[_j][_qp]*_grad_u[_qp] + dmob_dp*_grad_phi[_j][_qp]; Real ddiv_pot_dp = -(_permeability[_qp]*_grad_phi[_j][_qp])*_ddensity[_qp][_pvar]*_gravity[_qp] - (_permeability[_qp]*_grad_u[_qp])*_d2density[_qp][_pvar]*_phi[_j][_qp]*_gravity[_qp]; //ddiv_pot_dp += (_permeability[_qp]*_second_phi[_j][_qp]).tr(); // crashes because _second_phi_zero is not done correctly supg_kernel_prime = -dgrad_mob_dp*pot - grad_mob*dpot_dp - dmob_dp*_phi[_j][_qp]*div_pot - mob*ddiv_pot_dp; } return (dflux_dp + supg_test_prime*supg_kernel + supg_test*supg_kernel_prime)/_viscosity[_qp][_pvar]; }
void TransportSolverCompressibleTwophaseReorder::solveSingleCellGravity(const std::vector<int>& cells, const int pos, const double* gravflux) { const int cell = cells[pos]; GravityResidual res(*this, cells, pos, gravflux); if (std::fabs(res(saturation_[cell])) > tol_) { int iters_used; saturation_[cell] = RootFinder::solve(res, saturation_[cell], 0.0, 1.0, maxit_, tol_, iters_used); } mobility(saturation_[cell], cell, &mob_[2*cell]); }
void PorousFlowDarcyBase::harmonicMean(JacRes res_or_jac, unsigned int ph, unsigned int pvar) { // The number of nodes in the element const unsigned int num_nodes = _test.size(); std::vector<Real> mob(num_nodes); unsigned num_zero = 0; unsigned zero_mobility_node = std::numeric_limits<unsigned>::max(); Real harmonic_mob = 0; for (unsigned n = 0; n < num_nodes; ++n) { mob[n] = mobility(n, ph); if (mob[n] == 0.0) { zero_mobility_node = n; num_zero++; } else harmonic_mob += 1.0 / mob[n]; } if (num_zero > 0) harmonic_mob = 0.0; else harmonic_mob = (1.0 * num_nodes) / harmonic_mob; // d(harmonic_mob)/d(PorousFlow variable at node n) std::vector<Real> dharmonic_mob(num_nodes, 0.0); if (res_or_jac == JacRes::CALCULATE_JACOBIAN) { const Real harm2 = std::pow(harmonic_mob, 2) / (1.0 * num_nodes); if (num_zero == 0) for (unsigned n = 0; n < num_nodes; ++n) dharmonic_mob[n] = dmobility(n, ph, pvar) * harm2 / std::pow(mob[n], 2); else if (num_zero == 1) dharmonic_mob[zero_mobility_node] = num_nodes * dmobility(zero_mobility_node, ph, pvar); // other derivs are zero // if num_zero > 1 then all dharmonic_mob = 0.0 } if (res_or_jac == JacRes::CALCULATE_JACOBIAN) for (unsigned n = 0; n < num_nodes; ++n) for (_j = 0; _j < _phi.size(); _j++) { _jacobian[ph][n][_j] *= harmonic_mob; if (_test.size() == _phi.size()) _jacobian[ph][n][_j] += dharmonic_mob[_j] * _proto_flux[ph][n]; } if (res_or_jac == JacRes::CALCULATE_RESIDUAL) for (unsigned n = 0; n < num_nodes; ++n) _proto_flux[ph][n] *= harmonic_mob; }
void TransportSolverTwophaseReorder::solveSingleCellGravity(const std::vector<int>& cells, const int pos, const double* gravflux) { const int cell = cells[pos]; GravityResidual res(*this, cells, pos, gravflux); if (std::fabs(res(saturation_[cell])) > tol_) { int iters_used = 0; saturation_[cell] = RootFinder::solve(res, smin_[2*cell], smax_[2*cell], maxit_, tol_, iters_used); reorder_iterations_[cell] = reorder_iterations_[cell] + iters_used; } saturation_[cell] = std::min(std::max(saturation_[cell], smin_[2*cell]), smax_[2*cell]); mobility(saturation_[cell], cell, &mob_[2*cell]); }
void update(MMSP::grid<3,int>& grid, int steps) { const double kT = 0.75; int gss = int(sqrt(nodes(grid))); for (int step=0; step<steps; step++) { for (int h=0; h<nodes(grid); h++) { // choose a random node int p = rand()%nodes(grid); vector<int> x = position(grid,p); int spin1 = grid(p); if (spin1!=0) { // determine neighboring spins sparse<bool> neighbors; for (int i=-1; i<=1; i++) for (int j=-1; j<=1; j++) for (int k=-1; k<=1; k++) { int spin = grid[x[0]+i][x[1]+j][x[2]+k]; set(neighbors,spin) = true; } // choose a random neighbor spin int spin2 = index(neighbors,rand()%length(neighbors)); if (spin1!=spin2 and spin2!=0) { // compute energy change double dE = -energy(spin1,spin2); for (int i=-1; i<=1; i++) for (int j=-1; j<=1; j++) for (int k=-1; k<=1; k++) { int spin = grid[x[0]+i][x[1]+j][x[2]+k]; dE += energy(spin,spin2)-energy(spin,spin1); } // compute boundary energy, mobility double E = energy(spin1,spin2); double M = mobility(spin1,spin2); // attempt a spin flip double r = double(rand())/double(RAND_MAX); if (dE<=0.0 and r<M*E) grid(p) = spin2; if (dE>0.0 and r<M*E*exp(-dE/(E*kT))) grid(p) = spin2; } } if (h%gss==0) ghostswap(grid); } } }
void TransportSolverCompressibleTwophaseReorder::solveGravity(const std::vector<std::vector<int> >& columns, const double dt, std::vector<double>& saturation, std::vector<double>& surfacevol) { // Assume that solve() has already been called, so that A_ is current. initGravityDynamic(); // Initialize mobilities. const int nc = grid_.number_of_cells; std::vector<int> cells(nc); for (int c = 0; c < nc; ++c) { cells[c] = c; } mob_.resize(2*nc); // props_.relperm(cells.size(), &saturation[0], &cells[0], &mob_[0], 0); // props_.viscosity(props_.numCells(), pressure, NULL, &allcells_[0], &visc_[0], NULL); // for (int c = 0; c < nc; ++c) { // mob_[2*c + 0] /= visc_[2*c + 0]; // mob_[2*c + 1] /= visc_[2*c + 1]; // } const int np = props_.numPhases(); for (int cell = 0; cell < nc; ++cell) { mobility(saturation_[cell], cell, &mob_[np*cell]); } // Set up other variables. dt_ = dt; toWaterSat(saturation, saturation_); // Solve on all columns. int num_iters = 0; for (std::vector<std::vector<int> >::size_type i = 0; i < columns.size(); i++) { // std::cout << "==== new column" << std::endl; num_iters += solveGravityColumn(columns[i]); } std::cout << "Gauss-Seidel column solver average iterations: " << double(num_iters)/double(columns.size()) << std::endl; toBothSat(saturation_, saturation); // Compute surface volume as a postprocessing step from saturation and A_ computeSurfacevol(grid_.number_of_cells, props_.numPhases(), &A_[0], &saturation[0], &surfacevol[0]); }
Real RichardsFlux::computeQpResidual() { Real mob = mobility(_density[_qp][_pvar], _rel_perm[_qp][_pvar]); RealVectorValue pot = _permeability[_qp]*(_grad_u[_qp] - _density[_qp][_pvar]*_gravity[_qp]); Real flux_part = _grad_test[_i][_qp]*mob*pot; Real supg_test = _tauvel_SUPG[_qp][_pvar]*_grad_test[_i][_qp]; Real supg_kernel = 0.0; if (supg_test != 0) { Real dmob_dp = dmobility_dp(_density[_qp][_pvar], _ddensity[_qp][_pvar], _rel_perm[_qp][_pvar], _drel_perm[_qp][_pvar]*_dseff[_qp][_pvar][_pvar]); RealVectorValue grad_mob = dmob_dp*_grad_u[_qp]; // NOTE: since Libmesh does not correctly calculate grad(_grad_u) correctly, so following might not be correct Real div_pot = (_permeability[_qp]*_second_u[_qp]).tr() - (_permeability[_qp]*_grad_u[_qp])*_ddensity[_qp][_pvar]*_gravity[_qp]; supg_kernel = -grad_mob*pot - mob*div_pot; } return (flux_part + supg_test*supg_kernel)/_viscosity[_qp][_pvar]; }
void PorousFlowDarcyBase::quickUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar) { // The number of nodes in the element const unsigned int num_nodes = _test.size(); Real mob; Real dmob; // Use the raw nodal mobility for (unsigned int n = 0; n < num_nodes; ++n) { // The mobility at the 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]; } _proto_flux[ph][n] *= mob; } }
template <int dim> void update(grid<dim, sparse<phi_type> >& oldGrid, int steps) { int rank=0; #ifdef MPI_VERSION rank=MPI::COMM_WORLD.Get_rank(); #endif const phi_type dt = 0.01; const phi_type width = 14.5; const phi_type epsilon = 1.0e-8; const double mu_hi = 1.00; const double mu_lo = 0.01; const double mu_x = 0.6422; const double mu_s = 0.0175; std::ofstream vfile; if (rank==0) vfile.open("v.log",std::ofstream::out | std::ofstream::app); for (int step = 0; step < steps; step++) { if (rank==0) print_progress(step, steps); // newGrid grid must be overwritten each time ghostswap(oldGrid); grid<dim, sparse<phi_type> > newGrid(oldGrid); for (int d=0; d<dim; d++) { if (x0(oldGrid, d) == g0(oldGrid,d)) { b0(oldGrid,d) = Dirichlet; b0(newGrid,d) = Dirichlet; } else if (x1(oldGrid,d) == g1(oldGrid,d)) { b1(oldGrid,d) = Dirichlet; b1(newGrid,d) = Dirichlet; } } for (int i = 0; i < nodes(oldGrid); i++) { vector<int> x = position(oldGrid, i); // determine nonzero fields within // the neighborhood of this node // (2 adjacent voxels along each cardinal direction) sparse<int> s; for (int j = 0; j < dim; j++) for (int k = -1; k <= 1; k++) { x[j] += k; for (int h = 0; h < length(oldGrid(x)); h++) { int pindex = index(oldGrid(x), h); set(s, pindex) = 1; } x[j] -= k; } phi_type S = phi_type(length(s)); // if only one field is nonzero, // then copy this node to newGrid if (S < 2.0) newGrid(i) = oldGrid(i); else { // compute laplacian of each field sparse<phi_type> lap = laplacian(oldGrid, i); // compute variational derivatives sparse<phi_type> dFdp; for (int h = 0; h < length(s); h++) { int hindex = index(s, h); for (int j = h + 1; j < length(s); j++) { int jindex = index(s, j); phi_type gamma = energy(hindex, jindex); phi_type eps = 4.0 / acos(-1.0) * sqrt(0.5 * gamma * width); phi_type w = 4.0 * gamma / width; // Update dFdp_h and dFdp_j, so the inner loop can be over j>h instead of j≠h set(dFdp, hindex) += 0.5 * eps * eps * lap[jindex] + w * oldGrid(i)[jindex]; set(dFdp, jindex) += 0.5 * eps * eps * lap[hindex] + w * oldGrid(i)[hindex]; } } // compute time derivatives sparse<phi_type> dpdt; phi_type mu = mobility(mu_lo, mu_hi, mu_x, mu_s, oldGrid(x).getMagPhi()); for (int h = 0; h < length(s); h++) { int hindex = index(s, h); for (int j = h + 1; j < length(s); j++) { int jindex = index(s, j); set(dpdt, hindex) -= mu * (dFdp[hindex] - dFdp[jindex]); set(dpdt, jindex) -= mu * (dFdp[jindex] - dFdp[hindex]); } } // compute update values phi_type sum = 0.0; for (int h = 0; h < length(s); h++) { int pindex = index(s, h); phi_type value = oldGrid(i)[pindex] + dt * (2.0 / S) * dpdt[pindex]; // Extraneous factor of 2? if (value > 1.0) value = 1.0; if (value < 0.0) value = 0.0; if (value > epsilon) set(newGrid(i), pindex) = value; sum += newGrid(i)[pindex]; } // project onto Gibbs simplex (enforce Σφ=1) phi_type rsum = 0.0; if (fabs(sum) > 0.0) rsum = 1.0 / sum; for (int h = 0; h < length(newGrid(i)); h++) { int pindex = index(newGrid(i), h); set(newGrid(i), pindex) *= rsum; } } } // Loop over nodes(oldGrid) if ((step+1) % 10 == 0) { // Scan along just above the mid-line for the grain boundary. // When found, determine its angle. vector<int> x(dim, 0); const int offset = 2; const phi_type vert_mag = 1.0/std::sqrt(3.0); const phi_type edge_mag = 1.0/std::sqrt(2.0); const phi_type bulk_mag = 1.0; const phi_type edge_contour = edge_mag + 0.125*(bulk_mag - edge_mag); const phi_type vert_contour = vert_mag + 0.125*(edge_mag - vert_mag); x[0] = x0(newGrid,0); x[1] = (g1(newGrid,1) - g0(newGrid,1))/2; while (x[0]<x1(newGrid) && x[1]>=y0(newGrid) && x[1]<y1(newGrid) && newGrid(x).getMagPhi()>vert_contour) x[0]++; if (x[0] == x1(newGrid)) x[0] = g0(newGrid,0); int v0 = x[0]; #ifdef MPI_VERSION MPI::COMM_WORLD.Allreduce(&x[0], &v0, 1, MPI_INT, MPI_MAX); #endif x[1] += offset; while (x[0]>= x0(newGrid) && x[0]<x1(newGrid) && x[1]>=y0(newGrid) && x[1]<y1(newGrid) && newGrid(x).getMagPhi()>edge_contour) x[0]++; if (x[0] == x1(newGrid)) x[0] = g0(newGrid,0); int v1 = x[0]; #ifdef MPI_VERSION MPI::COMM_WORLD.Allreduce(&x[0], &v1, 1, MPI_INT, MPI_MAX); #endif x[1] += offset; while (x[0]>= x0(newGrid) && x[0]<x1(newGrid) && x[1]>=y0(newGrid) && x[1]<y1(newGrid) && newGrid(x).getMagPhi()>edge_contour) x[0]++; if (x[0] == x1(newGrid)) x[0] = g0(newGrid,0); int v2 = x[0]; #ifdef MPI_VERSION MPI::COMM_WORLD.Allreduce(&x[0], &v2, 1, MPI_INT, MPI_MAX); #endif // Second-order right-sided difference to approximate slope double diffX = 3.0*v0 - 4.0*v1 + 1.0*v2; double theta = 180.0/M_PI * std::atan2(2.0*offset*dx(newGrid,1), dx(newGrid,0)*diffX); if (rank==0) vfile << dx(newGrid,0)*v0 << '\t' << dx(newGrid,0)*v1 << '\t' << dx(newGrid,0)*v2 << '\t' << diffX << '\t' << theta << '\n'; } swap(oldGrid, newGrid); } // Loop over steps ghostswap(oldGrid); if (rank==0) vfile.close(); }
template <int dim> void update(grid<dim, sparse<phi_type> >& oldGrid, int steps) { #if (!defined MPI_VERSION) && (defined BGQ) std::cerr<<"Error: Blue Gene requires MPI."<<std::endl; exit(-1); #endif int rank=0; #ifdef MPI_VERSION rank = MPI::COMM_WORLD.Get_rank(); #endif const phi_type dt = 0.01; const phi_type width = 14.5; const phi_type epsilon = 1.0e-8; for (int step = 0; step < steps; step++) { if (rank==0) print_progress(step, steps); // newGrid must be overwritten each time ghostswap(oldGrid); grid<dim, sparse<phi_type> > newGrid(oldGrid); for (int i = 0; i < nodes(oldGrid); i++) { vector<int> x = position(oldGrid, i); // determine nonzero fields within // the neighborhood of this node // (2 adjacent voxels along each cardinal direction) sparse<int> s; for (int j = 0; j < dim; j++) for (int k = -1; k <= 1; k++) { x[j] += k; for (int h = 0; h < length(oldGrid(x)); h++) { int sindex = index(oldGrid(x), h); set(s, sindex) = 1; } x[j] -= k; } phi_type S = phi_type(length(s)); // if only one field is nonzero, // then copy this node to newGrid if (S < 2.0) newGrid(i) = oldGrid(i); else { // compute laplacian of each field sparse<phi_type> lap = laplacian(oldGrid, i); // compute variational derivatives sparse<phi_type> dFdp; for (int h = 0; h < length(s); h++) { int hindex = index(s, h); for (int j = h + 1; j < length(s); j++) { int jindex = index(s, j); phi_type gamma = energy(hindex, jindex); phi_type eps = 4.0 / acos(-1.0) * sqrt(0.5 * gamma * width); phi_type w = 4.0 * gamma / width; // Update dFdp_h and dFdp_j, so the inner loop can be over j>h instead of j≠h set(dFdp, hindex) += 0.5 * eps * eps * lap[jindex] + w * oldGrid(i)[jindex]; set(dFdp, jindex) += 0.5 * eps * eps * lap[hindex] + w * oldGrid(i)[hindex]; } } // compute time derivatives sparse<phi_type> dpdt; for (int h = 0; h < length(s); h++) { int hindex = index(s, h); for (int j = h + 1; j < length(s); j++) { int jindex = index(s, j); phi_type mu = mobility(hindex, jindex); set(dpdt, hindex) -= mu * (dFdp[hindex] - dFdp[jindex]); set(dpdt, jindex) -= mu * (dFdp[jindex] - dFdp[hindex]); } } // compute new values phi_type sum = 0.0; for (int h = 0; h < length(s); h++) { int sindex = index(s, h); phi_type value = oldGrid(i)[sindex] + dt * (2.0 / S) * dpdt[sindex]; // Extraneous factor of 2? if (value > 1.0) value = 1.0; if (value < 0.0) value = 0.0; if (value > epsilon) set(newGrid(i), sindex) = value; sum += newGrid(i)[sindex]; } // project onto Gibbs simplex (enforce Σφ=1) phi_type rsum = 0.0; if (fabs(sum) > 0.0) rsum = 1.0 / sum; for (int h = 0; h < length(newGrid(i)); h++) { int sindex = index(newGrid(i), h); set(newGrid(i), sindex) *= rsum; } } } // Loop over nodes(oldGrid) swap(oldGrid, newGrid); } // Loop over steps ghostswap(oldGrid); }
template <int dim> void update(MMSP::grid<dim,sparse<double> >& grid, int steps) { double dt = 0.01; double width = 8.0; for (int step=0; step<steps; step++) { print_progress(step, steps); // update grid must be overwritten each time MMSP::grid<dim,sparse<double> > update(grid); #ifndef MPI_VERSION #pragma omp parallel for #endif for (int n=0; n<nodes(grid); n++) { vector<int> x = position(grid,n); // compute laplacian of each field sparse<double> lapPhi = laplacian(grid,n); double S = double(length(lapPhi)); // if only one field is nonzero, // then copy this node to update if (S<2.0) update(x) = grid(n); else { // compute variational derivatives sparse<double> dFdp; for (int h=0; h<length(lapPhi); h++) { int hindex = MMSP::index(lapPhi,h); double phii = grid(n)[hindex]; for (int j=h+1; j<length(lapPhi); j++) { int jindex = MMSP::index(lapPhi,j); double phij = grid(n)[jindex]; double gamma = energy(hindex,jindex); double eps = 4.0/acos(-1.0)*sqrt(0.5*gamma*width); double w = 4.0*gamma/width; set(dFdp,hindex) += 0.5*eps*eps*lapPhi[jindex]+w*phij; set(dFdp,jindex) += 0.5*eps*eps*lapPhi[hindex]+w*phii; for (int k=j+1; k<length(lapPhi); k++) { int kindex = MMSP::index(lapPhi,k); double phik = grid(n)[kindex]; set(dFdp,hindex) += 3.0*phij*phik; set(dFdp,jindex) += 3.0*phii*phik; set(dFdp,kindex) += 3.0*phii*phij; } } } // compute time derivatives sparse<double> dpdt; for (int h=0; h<length(lapPhi); h++) { int hindex = MMSP::index(lapPhi,h); for (int j=h+1; j<length(lapPhi); j++) { int jindex = MMSP::index(lapPhi,j); double mu = mobility(hindex,jindex); set(dpdt,hindex) -= mu*(dFdp[hindex]-dFdp[jindex]); set(dpdt,jindex) -= mu*(dFdp[jindex]-dFdp[hindex]); } } // compute update values double sum = 0.0; for (int h=0; h<length(dpdt); h++) { int index = MMSP::index(dpdt,h); double value = grid(n)[index]+dt*(2.0/S)*dpdt[index]; if (value>1.0) value = 1.0; if (value<0.0) value = 0.0; if (value>machine_epsilon) set(update(x),index) = value; sum += update(x)[index]; } // project onto Gibbs simplex double rsum = (fabs(sum)>machine_epsilon)?1.0/sum:0.0; for (int h=0; h<length(update(x)); h++) { int index = MMSP::index(update(x),h); set(update(x),index) *= rsum; } } } swap(grid,update); ghostswap(grid); } MMSP::sparse<double> mass; for (int n=0; n<nodes(grid); n++) for (int l=0; l<length(grid(n)); l++) { int index = grid(n).index(l); set(mass,index) += grid(n)[index]; } for (int l=0; l<length(mass); l++) std::cout<<mass.value(l)<<'\t'; std::cout<<std::endl; }
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()); } } }
template <int dim> void update(MMSP::grid<dim,vector<double> >& grid, int steps) { MMSP::grid<dim,vector<double> > update(grid); double dt = 0.01; double width = 8.0; for (int step=0; step<steps; step++) { for (int i=0; i<nodes(grid); i++) { vector<int> x = position(grid,i); // determine nonzero fields within // the neighborhood of this node double S = 0.0; vector<int> s(fields(grid),0); for (int h=0; h<fields(grid); h++) { for (int j=0; j<dim; j++) for (int k=-1; k<=1; k++) { x[j] += k; if (grid(x)[h]>0.0) { s[h] = 1; x[j] -= k; goto next; } x[j] -= k; } next: S += s[h]; } // if only one field is nonzero, // then copy this node to update if (S<2.0) update(i) = grid(i); else { // compute laplacian of each field vector<double> lap = laplacian(grid,i); // compute variational derivatives vector<double> dFdp(fields(grid),0.0); for (int h=0; h<fields(grid); h++) if (s[h]>0.0) for (int j=h+1; j<fields(grid); j++) if (s[j]>0.0) { double gamma = energy(h,j); double eps = 4.0/acos(-1.0)*sqrt(0.5*gamma*width); double w = 4.0*gamma/width; dFdp[h] += 0.5*eps*eps*lap[j]+w*grid(i)[j]; dFdp[j] += 0.5*eps*eps*lap[h]+w*grid(i)[h]; for (int k=j+1; k<fields(grid); k++) if (s[k]>0.0) { dFdp[h] += 3.0*grid(i)[j]*grid(i)[k]; dFdp[j] += 3.0*grid(i)[k]*grid(i)[h]; dFdp[k] += 3.0*grid(i)[h]*grid(i)[j]; } } // compute time derivatives vector<double> dpdt(fields(grid),0.0); for (int h=0; h<fields(grid); h++) if (s[h]>0.0) for (int j=h+1; j<fields(grid); j++) if (s[j]>0.0) { double mu = mobility(h,j); dpdt[h] -= mu*(dFdp[h]-dFdp[j]); dpdt[j] -= mu*(dFdp[j]-dFdp[h]); } // compute update values double sum = 0.0; for (int h=0; h<fields(grid); h++) { double value = grid(i)[h]+dt*(2.0/S)*dpdt[h]; if (value>1.0) value = 1.0; if (value<0.0) value = 0.0; update(i)[h] = value; sum += value; } // project onto Gibbs simplex double rsum = 0.0; if (fabs(sum)>0.0) rsum = 1.0/sum; for (int h=0; h<fields(grid); h++) update(i)[h] *= rsum; } } swap(grid,update); ghostswap(grid); } }
// Static evaluation. Returns score score_t eval(position_t *p, bool verbose) { // seed rand_r with a value of 1, as per // http://linux.die.net/man/3/rand_r static __thread unsigned int seed = 1; // verbose = true: print out components of score ev_score_t score[2] = { 0, 0 }; // int corner[2][2] = { {INF, INF}, {INF, INF} }; ev_score_t bonus; //char buf[MAX_CHARS_IN_MOVE]; color_t c; for (fil_t f = 0; f < BOARD_WIDTH; f++) { for (rnk_t r = 0; r < BOARD_WIDTH; r++) { square_t sq = square_of(f, r); piece_t x = p->board[sq]; //if (verbose) { // square_to_str(sq, buf, MAX_CHARS_IN_MOVE); //} switch (ptype_of(x)) { case EMPTY: break; case PAWN: c = color_of(x); // MATERIAL heuristic: Bonus for each Pawn bonus = PAWN_EV_VALUE; // if (verbose) { // printf("MATERIAL bonus %d for %s Pawn on %s\n", bonus, color_to_str(c), buf); // } score[c] += bonus; // PBETWEEN heuristic bonus = pbetween(p, f, r); // if (verbose) { // printf("PBETWEEN bonus %d for %s Pawn on %s\n", bonus, color_to_str(c), buf); // } score[c] += bonus; // PCENTRAL heuristic bonus = pcentral(f, r); // if (verbose) { // printf("PCENTRAL bonus %d for %s Pawn on %s\n", bonus, color_to_str(c), buf); // } score[c] += bonus; break; case KING: c = color_of(x); // KFACE heuristic bonus = kface(p, f, r); // if (verbose) { // printf("KFACE bonus %d for %s King on %s\n", bonus, // color_to_str(c), buf); // } score[c] += bonus; // KAGGRESSIVE heuristic color_t othercolor = opp_color(c); square_t otherking = p->kloc[othercolor]; fil_t otherf = fil_of(otherking); rnk_t otherr = rnk_of(otherking); bonus = kaggressive(f, r, otherf, otherr); assert(bonus == kaggressive_old(p, f, r)); // if (verbose) { // printf("KAGGRESSIVE bonus %d for %s King on %s\n", bonus, color_to_str(c), buf); // } score[c] += bonus; break; case INVALID: break; default: tbassert(false, "Jose says: no way!\n"); // No way, Jose! } laser_map_black[sq] = 0; laser_map_white[sq] = 0; } } int black_pawns_unpinned = mark_laser_path(p, laser_map_white, WHITE, 1); // 1 = path of laser with no moves ev_score_t w_hattackable = HATTACK * (int) h_attackable; score[WHITE] += w_hattackable; // if (verbose) { // printf("HATTACK bonus %d for White\n", w_hattackable); // } // PAWNPIN Heuristic --- is a pawn immobilized by the enemy laser. int b_pawnpin = PAWNPIN * black_pawns_unpinned; score[BLACK] += b_pawnpin; int b_mobility = MOBILITY * mobility(p, BLACK); score[BLACK] += b_mobility; // if (verbose) { // printf("MOBILITY bonus %d for Black\n", b_mobility); // } int white_pawns_unpinned = mark_laser_path(p, laser_map_black, BLACK, 1); // 1 = path of laser with no moves ev_score_t b_hattackable = HATTACK * (int) h_attackable; score[BLACK] += b_hattackable; // if (verbose) { // printf("HATTACK bonus %d for Black\n", b_hattackable); // } int w_mobility = MOBILITY * mobility(p, WHITE); score[WHITE] += w_mobility; // if (verbose) { // printf("MOBILITY bonus %d for White\n", w_mobility); // } int w_pawnpin = PAWNPIN * white_pawns_unpinned; score[WHITE] += w_pawnpin; // score from WHITE point of view ev_score_t tot = score[WHITE] - score[BLACK]; if (RANDOMIZE) { ev_score_t z = rand_r(&seed) % (RANDOMIZE*2+1); tot = tot + z - RANDOMIZE; } if (color_to_move_of(p) == BLACK) { tot = -tot; } return tot / EV_SCORE_RATIO; }
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; } } }