Example #1
0
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]);
}
Example #3
0
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]);
 }
Example #5
0
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]);
}
Example #7
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];
}
Example #8
0
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;
}
Example #12
0
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());
    }
  }
}
Example #13
0
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;
}
Example #15
0
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;
    }
  }
}