inline void BlackoilPropertiesFromDeck::init(Opm::DeckConstPtr deck, Opm::EclipseStateConstPtr eclState, int number_of_cells, const int* global_cell, const int* cart_dims, const CentroidIterator& begin_cell_centroids, int dimension, const parameter::ParameterGroup& param, bool init_rock) { // retrieve the cell specific PVT table index from the deck // and using the grid... extractPvtTableIndex(cellPvtRegionIdx_, deck, number_of_cells, global_cell); if(init_rock){ rock_.init(eclState, number_of_cells, global_cell, cart_dims); } const int pvt_samples = param.getDefault("pvt_tab_size", -1); pvt_.init(deck, pvt_samples); // Unfortunate lack of pointer smartness here... const int sat_samples = param.getDefault("sat_tab_size", -1); std::string threephase_model = param.getDefault<std::string>("threephase_model", "gwseg"); if (deck->hasKeyword("ENDSCALE") && threephase_model != "gwseg") { OPM_THROW(std::runtime_error, "Sorry, end point scaling currently available for the 'gwseg' model only."); } if (sat_samples > 1) { if (threephase_model == "stone2") { SaturationPropsFromDeck<SatFuncStone2Uniform>* ptr = new SaturationPropsFromDeck<SatFuncStone2Uniform>(); satprops_.reset(ptr); ptr->init(deck, eclState, number_of_cells, global_cell, begin_cell_centroids, dimension, sat_samples); } else if (threephase_model == "simple") { SaturationPropsFromDeck<SatFuncSimpleUniform>* ptr = new SaturationPropsFromDeck<SatFuncSimpleUniform>(); satprops_.reset(ptr); ptr->init(deck, eclState, number_of_cells, global_cell, begin_cell_centroids, dimension, sat_samples); } else if (threephase_model == "gwseg") { SaturationPropsFromDeck<SatFuncGwsegUniform>* ptr = new SaturationPropsFromDeck<SatFuncGwsegUniform>(); satprops_.reset(ptr); ptr->init(deck, eclState, number_of_cells, global_cell, begin_cell_centroids, dimension, sat_samples); } else { OPM_THROW(std::runtime_error, "Unknown threephase_model: " << threephase_model); } } else { if (threephase_model == "stone2") { SaturationPropsFromDeck<SatFuncStone2Nonuniform>* ptr = new SaturationPropsFromDeck<SatFuncStone2Nonuniform>(); satprops_.reset(ptr); ptr->init(deck, eclState, number_of_cells, global_cell, begin_cell_centroids, dimension, sat_samples); } else if (threephase_model == "simple") { SaturationPropsFromDeck<SatFuncSimpleNonuniform>* ptr = new SaturationPropsFromDeck<SatFuncSimpleNonuniform>(); satprops_.reset(ptr); ptr->init(deck, eclState, number_of_cells, global_cell, begin_cell_centroids, dimension, sat_samples); } else if (threephase_model == "gwseg") { SaturationPropsFromDeck<SatFuncGwsegNonuniform>* ptr = new SaturationPropsFromDeck<SatFuncGwsegNonuniform>(); satprops_.reset(ptr); ptr->init(deck, eclState, number_of_cells, global_cell, begin_cell_centroids, dimension, sat_samples); } else { OPM_THROW(std::runtime_error, "Unknown threephase_model: " << threephase_model); } } if (pvt_.numPhases() != satprops_->numPhases()) { OPM_THROW(std::runtime_error, "BlackoilPropertiesFromDeck::BlackoilPropertiesFromDeck() - Inconsistent number of phases in pvt data (" << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_->numPhases() << ")."); } }
static void saturations(ContainerT& /* values */, const Params& /* params */, const FluidState& /* fs */) { OPM_THROW(std::logic_error, "Not implemented: saturations()"); }
void testCommon(const Spline &sp, const double *x, const double *y) { static double eps = 1e-10; static double epsFD = 1e-7; size_t n = sp.numSamples(); for (size_t i = 0; i < n; ++i) { // sure that we hit all sampling points double y0 = (i>0)?sp.eval(x[i]-eps):y[0]; double y1 = sp.eval(x[i]); double y2 = (i<n-1)?sp.eval(x[i]+eps):y[n-1]; if (std::abs(y0 - y[i]) > 100*eps || std::abs(y2 - y[i]) > 100*eps) OPM_THROW(std::runtime_error, "Spline seems to be discontinuous at sampling point " << i << "!"); if (std::abs(y1 - y[i]) > eps) OPM_THROW(std::runtime_error, "Spline does not capture sampling point " << i << "!"); // make sure the derivative is continuous (assuming that the // second derivative is smaller than 1000) double d1 = sp.evalDerivative(x[i]); double d0 = (i>0)?sp.evalDerivative(x[i]-eps):d1; double d2 = (i<n-1)?sp.evalDerivative(x[i]+eps):d1; if (std::abs(d1 - d0) > 1000*eps || std::abs(d2 - d0) > 1000*eps) OPM_THROW(std::runtime_error, "Spline seems to exhibit a discontinuous derivative at sampling point " << i << "!"); } // make sure the derivatives are consistent with the curve size_t np = 3*n; for (size_t i = 0; i < np; ++i) { double xval = sp.xMin() + (sp.xMax() - sp.xMin())*i/np; // first derivative double y1 = sp.eval(xval+epsFD); double y0 = sp.eval(xval); double mFD = (y1 - y0)/epsFD; double m = sp.evalDerivative(xval); if (std::abs( mFD - m ) > 1000*epsFD) OPM_THROW(std::runtime_error, "Derivative of spline seems to be inconsistent with cuve" " (" << mFD << " - " << m << " = " << mFD - m << ")!"); // second derivative y1 = sp.evalDerivative(xval+epsFD); y0 = sp.evalDerivative(xval); mFD = (y1 - y0)/epsFD; m = sp.evalSecondDerivative(xval); if (std::abs( mFD - m ) > 1000*epsFD) OPM_THROW(std::runtime_error, "Second derivative of spline seems to be inconsistent with cuve" " (" << mFD << " - " << m << " = " << mFD - m << ")!"); // Third derivative y1 = sp.evalSecondDerivative(xval+epsFD); y0 = sp.evalSecondDerivative(xval); mFD = (y1 - y0)/epsFD; m = sp.evalThirdDerivative(xval); if (std::abs( mFD - m ) > 1000*epsFD) OPM_THROW(std::runtime_error, "Third derivative of spline seems to be inconsistent with cuve" " (" << mFD << " - " << m << " = " << mFD - m << ")!"); } }
/// Solve for time-of-flight and a number of tracers. /// \param[in] darcyflux Array of signed face fluxes. /// \param[in] porevolume Array of pore volumes. /// \param[in] source Source term. Sign convention is: /// (+) inflow flux, /// (-) outflow flux. /// \param[in] tracerheads Table containing one row per tracer, and each /// row contains the source cells for that tracer. /// \param[out] tof Array of time-of-flight values (1 per cell). /// \param[out] tracer Array of tracer values. N per cell, where N is /// equalt to tracerheads.size(). void TofReorder::solveTofTracer(const double* darcyflux, const double* porevolume, const double* source, const SparseTable<int>& tracerheads, std::vector<double>& tof, std::vector<double>& tracer) { darcyflux_ = darcyflux; porevolume_ = porevolume; source_ = source; const int num_cells = grid_.number_of_cells; #ifndef NDEBUG // Sanity check for sources. const double cum_src = std::accumulate(source, source + num_cells, 0.0); if (std::fabs(cum_src) > *std::max_element(source, source + num_cells)*1e-2) { OPM_THROW(std::runtime_error, "Sources do not sum to zero: " << cum_src); } #endif tof.resize(num_cells); std::fill(tof.begin(), tof.end(), 0.0); tof_ = &tof[0]; if (use_multidim_upwind_) { face_tof_.resize(grid_.number_of_faces); std::fill(face_tof_.begin(), face_tof_.end(), 0.0); face_part_tof_.resize(grid_.face_nodepos[grid_.number_of_faces]); std::fill(face_part_tof_.begin(), face_part_tof_.end(), 0.0); } // Execute solve for tof compute_tracer_ = false; executeSolve(); // Find the tracer heads (injectors). const int num_tracers = tracerheads.size(); tracer.resize(num_cells*num_tracers); std::fill(tracer.begin(), tracer.end(), 0.0); if (num_tracers > 0) { tracerhead_by_cell_.clear(); tracerhead_by_cell_.resize(num_cells, NoTracerHead); } for (int tr = 0; tr < num_tracers; ++tr) { const unsigned int tracerheadsSize = tracerheads[tr].size(); for (unsigned int i = 0; i < tracerheadsSize; ++i) { const int cell = tracerheads[tr][i]; tracer[num_cells * tr + cell] = 1.0; tracerhead_by_cell_[cell] = tr; } } // Execute solve for tracers. std::vector<double> fake_pv(num_cells, 0.0); porevolume_ = fake_pv.data(); for (int tr = 0; tr < num_tracers; ++tr) { tof_ = tracer.data() + tr * num_cells; compute_tracer_ = true; executeSolve(); } // Write output tracer data (transposing the computed data). std::vector<double> computed = tracer; for (int cell = 0; cell < num_cells; ++cell) { for (int tr = 0; tr < num_tracers; ++tr) { tracer[num_tracers * cell + tr] = computed[num_cells * tr + cell]; } } }
// dummy method that is not implemented for this class SolutionVector computeNewtonIncrement(const LinearisedBlackoilResidual&) const { OPM_THROW(std::logic_error,"This method is not implemented"); return SolutionVector(); }
SimulatorCompressiblePolymer::Impl::Impl(const parameter::ParameterGroup& param, const UnstructuredGrid& grid, const BlackoilPropertiesInterface& props, const PolymerProperties& poly_props, const RockCompressibility* rock_comp_props, WellsManager& wells_manager, const PolymerInflowInterface& polymer_inflow, LinearSolverInterface& linsolver, const double* gravity) : grid_(grid), props_(props), poly_props_(poly_props), rock_comp_props_(rock_comp_props), wells_manager_(wells_manager), wells_(wells_manager.c_wells()), polymer_inflow_(polymer_inflow), gravity_(gravity), psolver_(grid, props, rock_comp_props, poly_props, linsolver, param.getDefault("nl_pressure_residual_tolerance", 0.0), param.getDefault("nl_pressure_change_tolerance", 1.0), param.getDefault("nl_pressure_maxiter", 10), gravity, wells_manager.c_wells()), tsolver_(grid, props, poly_props, TransportSolverTwophaseCompressiblePolymer::Bracketing, param.getDefault("nl_tolerance", 1e-9), param.getDefault("nl_maxiter", 30)) { // For output. output_ = param.getDefault("output", true); if (output_) { output_vtk_ = param.getDefault("output_vtk", true); output_dir_ = param.getDefault("output_dir", std::string("output")); // Ensure that output dir exists boost::filesystem::path fpath(output_dir_); try { create_directories(fpath); } catch (...) { OPM_THROW(std::runtime_error, "Creating directories failed: " << fpath); } output_interval_ = param.getDefault("output_interval", 1); } // Well control related init. check_well_controls_ = param.getDefault("check_well_controls", false); max_well_control_iterations_ = param.getDefault("max_well_control_iterations", 10); // Transport related init. TransportSolverTwophaseCompressiblePolymer::SingleCellMethod method; std::string method_string = param.getDefault("single_cell_method", std::string("Bracketing")); if (method_string == "Bracketing") { method = Opm::TransportSolverTwophaseCompressiblePolymer::Bracketing; } else if (method_string == "Newton") { method = Opm::TransportSolverTwophaseCompressiblePolymer::Newton; } else { OPM_THROW(std::runtime_error, "Unknown method: " << method_string); } tsolver_.setPreferredMethod(method); num_transport_substeps_ = param.getDefault("num_transport_substeps", 1); use_segregation_split_ = param.getDefault("use_segregation_split", false); if (gravity != 0 && use_segregation_split_) { tsolver_.initGravity(gravity); extractColumn(grid_, columns_); } // Misc init. const int num_cells = grid.number_of_cells; allcells_.resize(num_cells); for (int cell = 0; cell < num_cells; ++cell) { allcells_[cell] = cell; } }
inline int MinpvProcessor::process(const std::vector<double>& pv, const double minpv, const std::vector<int>& actnum, const bool mergeMinPVCells, double* zcorn) const { // Algorithm: // 1. Process each column of cells (with same i and j // coordinates) from top (low k) to bottom (high k). // 2. For each cell 'c' visited, check if its pore volume // pv[c] is less than minpv. // 3. If below the minpv threshold, move the lower four // zcorn associated with the cell c to coincide with // the upper four (so it becomes degenerate). If mergeMinPVcells // is true, the higher four zcorn associated with the cell below // is moved to these values (so it gains the deleted volume). int cells_modified = 0; // Check for sane input sizes. const size_t log_size = dims_[0] * dims_[1] * dims_[2]; if (pv.size() != log_size) { OPM_THROW(std::runtime_error, "Wrong size of PORV input, must have one element per logical cartesian cell."); } if (!actnum.empty() && actnum.size() != log_size) { OPM_THROW(std::runtime_error, "Wrong size of ACTNUM input, must have one element per logical cartesian cell."); } // Main loop. for (int kk = 0; kk < dims_[2]; ++kk) { for (int jj = 0; jj < dims_[1]; ++jj) { for (int ii = 0; ii < dims_[0]; ++ii) { const int c = ii + dims_[0] * (jj + dims_[1] * kk); if (pv[c] < minpv && (actnum.empty() || actnum[c])) { // Move deeper (higher k) coordinates to lower k coordinates. std::array<double, 8> cz = getCellZcorn(ii, jj, kk, zcorn); for (int count = 0; count < 4; ++count) { cz[count + 4] = cz[count]; } setCellZcorn(ii, jj, kk, cz, zcorn); // optionally add removed volume to the cell below. if (mergeMinPVCells) { // Check if there is a cell below. if (pv[c] > 0.0 && kk < dims_[2] - 1) { // Set lower k coordinates of cell below to upper cells's coordinates. std::array<double, 8> cz_below = getCellZcorn(ii, jj, kk + 1, zcorn); for (int count = 0; count < 4; ++count) { cz_below[count] = cz[count]; } setCellZcorn(ii, jj, kk + 1, cz_below, zcorn); } } ++cells_modified; } } } } return cells_modified; }
static Evaluation liquidDiffCoeff(const Evaluation& /*temperature*/, const Evaluation& /*pressure*/) { OPM_THROW(std::runtime_error, "Not implemented: Binary liquid diffusion coefficients of air and mesitylene"); }
void IntegrationTest::getIntegrationTest(){ std::vector<double> timeVec1, timeVec2; setTimeVecs(timeVec1, timeVec2); // Sets the time vectors, they are equal for all keywords (WPOR:PROD01 etc) setDataSets(timeVec1, timeVec2); int ivar = 0; if(!allowDifferentAmountOfKeywords){ if(stringlist_get_size(keysShort) != stringlist_get_size(keysLong)){ OPM_THROW(std::invalid_argument, "Different ammont of keywords in the two summary files."); } } if(printKeyword){ printKeywords(); return; } std::string keywordWithGreatestErrorRatio; double greatestRatio = 0; //Iterates over all keywords from the restricted file, use iterator "ivar". Searches for a match in the file with more keywords, use the itarator "jvar". while(ivar < stringlist_get_size(keysShort)){ const char* keyword = stringlist_iget(keysShort, ivar); if(oneOfTheMainVariables){ std::string keywordString(keyword); std::string substr = keywordString.substr(0,4); if(substr!= mainVariable){ ivar++; continue; } } for (int jvar = 0; jvar < stringlist_get_size(keysLong); jvar++){ if (strcmp(keyword, stringlist_iget(keysLong, jvar)) == 0){ //When the keywords are equal, proceed in comparing summary files. /* if(!checkUnits(keyword)){ OPM_THROW(std::runtime_error, "For keyword " << keyword << " the unit of the two files is not equal. Not possible to compare."); } //Comparing the unit of the two vectors.*/ checkForKeyword(timeVec1, timeVec2, keyword); if(findVectorWithGreatestErrorRatio){ WellProductionVolume volume = getSpecificWellVolume(timeVec1,timeVec2, keyword); findGreatestErrorRatio(volume,greatestRatio, keyword, keywordWithGreatestErrorRatio); } break; } //will only enter here if no keyword match if(jvar == stringlist_get_size(keysLong)-1){ if(!allowDifferentAmountOfKeywords){ OPM_THROW(std::invalid_argument, "No match on keyword"); } } } ivar++; } if(findVectorWithGreatestErrorRatio){ std::cout << "The keyword " << keywordWithGreatestErrorRatio << " had the greatest error ratio, which was " << greatestRatio << std::endl; } if((findVolumeError || oneOfTheMainVariables) && !findVectorWithGreatestErrorRatio){ evaluateWellProductionVolume(); } if(allowSpikes){ std::cout << "checkWithSpikes succeeded." << std::endl; } }
/*! * \brief The temperature of a fluid phase [-] */ const Scalar& temperature(unsigned /* phaseIdx */) const { OPM_THROW(std::runtime_error, "Temperature is not provided by this fluid state"); }
static Evaluation henry(const Evaluation& /*temperature*/) { OPM_THROW(std::runtime_error, "Not implemented: Henry coefficient of air in mesitylene"); }
SimulatorReport SimulatorIncompTwophase::Impl::run(SimulatorTimer& timer, TwophaseState& state, WellState& well_state) { std::vector<double> transport_src; // Initialisation. std::vector<double> porevol; if (rock_comp_props_ && rock_comp_props_->isActive()) { computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol); } else { computePorevolume(grid_, props_.porosity(), porevol); } const double tot_porevol_init = std::accumulate(porevol.begin(), porevol.end(), 0.0); std::vector<double> initial_porevol = porevol; // Main simulation loop. Opm::time::StopWatch pressure_timer; double ptime = 0.0; Opm::time::StopWatch transport_timer; double ttime = 0.0; Opm::time::StopWatch callback_timer; double time_in_callbacks = 0.0; Opm::time::StopWatch step_timer; Opm::time::StopWatch total_timer; total_timer.start(); double init_satvol[2] = { 0.0 }; double satvol[2] = { 0.0 }; double tot_injected[2] = { 0.0 }; double tot_produced[2] = { 0.0 }; Opm::computeSaturatedVol(porevol, state.saturation(), init_satvol); *log_ << "\nInitial saturations are " << init_satvol[0]/tot_porevol_init << " " << init_satvol[1]/tot_porevol_init << std::endl; Opm::Watercut watercut; watercut.push(0.0, 0.0, 0.0); Opm::WellReport wellreport; std::vector<double> fractional_flows; std::vector<double> well_resflows_phase; if (wells_) { well_resflows_phase.resize((wells_->number_of_phases)*(wells_->number_of_wells), 0.0); wellreport.push(props_, *wells_, state.saturation(), 0.0, well_state.bhp(), well_state.perfRates()); } std::fstream tstep_os; if (output_) { std::string filename = output_dir_ + "/step_timing.param"; tstep_os.open(filename.c_str(), std::fstream::out | std::fstream::app); } while (!timer.done()) { // Report timestep and (optionally) write state to disk. step_timer.start(); timer.report(*log_); if (output_ && (timer.currentStepNum() % output_interval_ == 0)) { if (output_vtk_) { #ifdef DISABLE_OUTPUT outputStateVtk(grid_, state, timer.currentStepNum(), output_dir_); #endif } #ifdef DISABLE_OUTPUT outputStateMatlab(grid_, state, timer.currentStepNum(), output_dir_); #endif if (use_reorder_) { // This use of dynamic_cast is not ideal, but should be safe. outputVectorMatlab(std::string("reorder_it"), dynamic_cast<const TransportSolverTwophaseReorder&>(*tsolver_).getReorderIterations(), timer.currentStepNum(), output_dir_); } } SimulatorReport sreport; // Solve pressure equation. if (check_well_controls_) { computeFractionalFlow(props_, allcells_, state.saturation(), fractional_flows); wells_manager_.applyExplicitReinjectionControls(well_resflows_phase, well_resflows_phase); } bool well_control_passed = !check_well_controls_; int well_control_iteration = 0; do { // Run solver. pressure_timer.start(); std::vector<double> initial_pressure = state.pressure(); psolver_.solve(timer.currentStepLength(), state, well_state); // Renormalize pressure if rock is incompressible, and // there are no pressure conditions (bcs or wells). // It is deemed sufficient for now to renormalize // using geometric volume instead of pore volume. if ((rock_comp_props_ == NULL || !rock_comp_props_->isActive()) && allNeumannBCs(bcs_) && allRateWells(wells_)) { // Compute average pressures of previous and last // step, and total volume. double av_prev_press = 0.0; double av_press = 0.0; double tot_vol = 0.0; const int num_cells = grid_.number_of_cells; for (int cell = 0; cell < num_cells; ++cell) { av_prev_press += initial_pressure[cell]*grid_.cell_volumes[cell]; av_press += state.pressure()[cell]*grid_.cell_volumes[cell]; tot_vol += grid_.cell_volumes[cell]; } // Renormalization constant const double ren_const = (av_prev_press - av_press)/tot_vol; for (int cell = 0; cell < num_cells; ++cell) { state.pressure()[cell] += ren_const; } const int num_wells = (wells_ == NULL) ? 0 : wells_->number_of_wells; for (int well = 0; well < num_wells; ++well) { well_state.bhp()[well] += ren_const; } } // Stop timer and report. pressure_timer.stop(); double pt = pressure_timer.secsSinceStart(); *log_ << "Pressure solver took: " << pt << " seconds." << std::endl; ptime += pt; sreport.pressure_time = pt; // Optionally, check if well controls are satisfied. if (check_well_controls_) { Opm::computePhaseFlowRatesPerWell(*wells_, well_state.perfRates(), fractional_flows, well_resflows_phase); *log_ << "Checking well conditions." << std::endl; // For testing we set surface := reservoir well_control_passed = wells_manager_.conditionsMet(well_state.bhp(), well_resflows_phase, well_resflows_phase); ++well_control_iteration; if (!well_control_passed && well_control_iteration > max_well_control_iterations_) { OPM_THROW(std::runtime_error, "Could not satisfy well conditions in " << max_well_control_iterations_ << " tries."); } if (!well_control_passed) { *log_ << "Well controls not passed, solving again." << std::endl; } else { *log_ << "Well conditions met." << std::endl; } } } while (!well_control_passed); // Update pore volumes if rock is compressible. if (rock_comp_props_ && rock_comp_props_->isActive()) { initial_porevol = porevol; computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol); } // Process transport sources (to include bdy terms and well flows). Opm::computeTransportSource(grid_, src_, state.faceflux(), 1.0, wells_, well_state.perfRates(), transport_src); // Solve transport. transport_timer.start(); double stepsize = timer.currentStepLength(); if (num_transport_substeps_ != 1) { stepsize /= double(num_transport_substeps_); *log_ << "Making " << num_transport_substeps_ << " transport substeps." << std::endl; } double injected[2] = { 0.0 }; double produced[2] = { 0.0 }; for (int tr_substep = 0; tr_substep < num_transport_substeps_; ++tr_substep) { tsolver_->solve(&initial_porevol[0], &transport_src[0], stepsize, state); double substep_injected[2] = { 0.0 }; double substep_produced[2] = { 0.0 }; Opm::computeInjectedProduced(props_, state.saturation(), transport_src, stepsize, substep_injected, substep_produced); injected[0] += substep_injected[0]; injected[1] += substep_injected[1]; produced[0] += substep_produced[0]; produced[1] += substep_produced[1]; if (use_reorder_ && use_segregation_split_) { // Again, unfortunate but safe use of dynamic_cast. // Possible solution: refactor gravity solver to its own class. dynamic_cast<TransportSolverTwophaseReorder&>(*tsolver_) .solveGravity(&initial_porevol[0], stepsize, state); } watercut.push(timer.simulationTimeElapsed() + timer.currentStepLength(), produced[0]/(produced[0] + produced[1]), tot_produced[0]/tot_porevol_init); if (wells_) { wellreport.push(props_, *wells_, state.saturation(), timer.simulationTimeElapsed() + timer.currentStepLength(), well_state.bhp(), well_state.perfRates()); } } transport_timer.stop(); double tt = transport_timer.secsSinceStart(); sreport.transport_time = tt; *log_ << "Transport solver took: " << tt << " seconds." << std::endl; ttime += tt; // Report volume balances. Opm::computeSaturatedVol(porevol, state.saturation(), satvol); tot_injected[0] += injected[0]; tot_injected[1] += injected[1]; tot_produced[0] += produced[0]; tot_produced[1] += produced[1]; reportVolumes(*log_, satvol, tot_porevol_init, tot_injected, tot_produced, injected, produced, init_satvol); sreport.total_time = step_timer.secsSinceStart(); if (output_) { sreport.reportParam(tstep_os); } // advance the timer to the end of the timestep *before* notifying // the client that the timestep is done ++timer; // notify all clients that we are done with the timestep callback_timer.start (); timestep_completed_.signal (); callback_timer.stop (); time_in_callbacks += callback_timer.secsSinceStart (); } if (output_) { if (output_vtk_) { #ifdef DISABLE_OUTPUT outputStateVtk(grid_, state, timer.currentStepNum(), output_dir_); #endif } #ifdef DISABLE_OUTPUT outputStateMatlab(grid_, state, timer.currentStepNum(), output_dir_); #endif if (use_reorder_) { // This use of dynamic_cast is not ideal, but should be safe. outputVectorMatlab(std::string("reorder_it"), dynamic_cast<const TransportSolverTwophaseReorder&>(*tsolver_).getReorderIterations(), timer.currentStepNum(), output_dir_); } outputWaterCut(watercut, output_dir_); if (wells_) { outputWellReport(wellreport, output_dir_); } tstep_os.close(); } total_timer.stop(); SimulatorReport report; report.pressure_time = ptime; report.transport_time = ttime; report.total_time = total_timer.secsSinceStart() - time_in_callbacks; return report; }
SimulatorIncompTwophase::Impl::Impl(const parameter::ParameterGroup& param, const UnstructuredGrid& grid, const IncompPropertiesInterface& props, const RockCompressibility* rock_comp_props, WellsManager& wells_manager, const std::vector<double>& src, const FlowBoundaryConditions* bcs, LinearSolverInterface& linsolver, const double* gravity) : use_reorder_(param.getDefault("use_reorder", true)), use_segregation_split_(param.getDefault("use_segregation_split", false)), grid_(grid), props_(props), rock_comp_props_(rock_comp_props), wells_manager_(wells_manager), wells_(wells_manager.c_wells()), src_(src), bcs_(bcs), psolver_(grid, props, rock_comp_props, linsolver, param.getDefault("nl_pressure_residual_tolerance", 0.0), param.getDefault("nl_pressure_change_tolerance", 1.0), param.getDefault("nl_pressure_maxiter", 10), gravity, wells_manager.c_wells(), src, bcs) { // Initialize transport solver. if (use_reorder_) { tsolver_.reset(new Opm::TransportSolverTwophaseReorder(grid, props, use_segregation_split_ ? gravity : NULL, param.getDefault("nl_tolerance", 1e-9), param.getDefault("nl_maxiter", 30))); } else { if (rock_comp_props && rock_comp_props->isActive()) { OPM_THROW(std::runtime_error, "The implicit pressure solver cannot handle rock compressibility."); } if (use_segregation_split_) { OPM_THROW(std::runtime_error, "The implicit pressure solver is not set up to use segregation splitting."); } std::vector<double> porevol; computePorevolume(grid, props.porosity(), porevol); tsolver_.reset(new Opm::TransportSolverTwophaseImplicit(grid, props, porevol, gravity, psolver_.getHalfTrans(), param)); } // For output. log_ = param.getDefault("quiet", false) ? &Opm::null_stream : &std::cout; output_ = param.getDefault("output", true); if (output_) { output_vtk_ = param.getDefault("output_vtk", true); output_dir_ = param.getDefault("output_dir", std::string("output")); // Ensure that output dir exists boost::filesystem::path fpath(output_dir_); try { create_directories(fpath); } catch (...) { OPM_THROW(std::runtime_error, "Creating directories failed: " << fpath); } output_interval_ = param.getDefault("output_interval", 1); } // Well control related init. check_well_controls_ = param.getDefault("check_well_controls", false); max_well_control_iterations_ = param.getDefault("max_well_control_iterations", 10); // Transport related init. num_transport_substeps_ = param.getDefault("num_transport_substeps", 1); // Misc init. const int num_cells = grid.number_of_cells; allcells_.resize(num_cells); for (int cell = 0; cell < num_cells; ++cell) { allcells_[cell] = cell; } }
void upscale(const Opm::parameter::ParameterGroup& param) { // Control structure. std::vector<double> saturations; Opm::SparseTable<double> all_pdrops; bool from_file = param.has("sat_pdrop_filename"); if (from_file) { std::string filename = param.get<std::string>("sat_pdrop_filename"); std::ifstream file(filename.c_str()); if (!file) { OPM_THROW(std::runtime_error, "Could not open file " << filename); } readControl(file, saturations, all_pdrops); } else { // Get a linear range of saturations. int num_sats = param.getDefault("num_sats", 4); double min_sat = param.getDefault("min_sat", 0.2); double max_sat = param.getDefault("max_sat", 0.8); saturations.resize(num_sats); for (int i = 0; i < num_sats; ++i) { double factor = num_sats == 1 ? 0 : double(i)/double(num_sats - 1); saturations[i] = (1.0 - factor)*min_sat + factor*max_sat; } // Get a logarithmic range of pressure drops. int num_pdrops = param.getDefault("num_pdrops", 5); double log_min_pdrop = std::log(param.getDefault("min_pdrop", 1e2)); double log_max_pdrop = std::log(param.getDefault("max_pdrop", 1e6)); std::vector<double> pdrops; pdrops.resize(num_pdrops); for (int i = 0; i < num_pdrops; ++i) { double factor = num_pdrops == 1 ? 0 : double(i)/double(num_pdrops - 1); pdrops[i] = std::exp((1.0 - factor)*log_min_pdrop + factor*log_max_pdrop); } // Assign the same pressure drops to all saturations. for (int i = 0; i < num_sats; ++i) { all_pdrops.appendRow(pdrops.begin(), pdrops.end()); } } int flow_direction = param.getDefault("flow_direction", 0); // Print the saturations and pressure drops. // writeControl(std::cout, saturations, all_pdrops); // Initialize upscaler. typedef SteadyStateUpscaler<Traits> Upscaler; typedef typename Upscaler::permtensor_t permtensor_t; Upscaler upscaler; upscaler.init(param); // First, compute an upscaled permeability. permtensor_t upscaled_K = upscaler.upscaleSinglePhase(); permtensor_t upscaled_K_copy = upscaled_K; upscaled_K_copy *= (1.0/(Opm::prefix::milli*Opm::unit::darcy)); std::cout.precision(15); std::cout << "Upscaled K in millidarcy:\n" << upscaled_K_copy << std::endl; std::cout << "Upscaled porosity: " << upscaler.upscalePorosity() << std::endl; // Create output streams for upscaled relative permeabilities std::string kr_filename = param.getDefault<std::string>("kr_filename", "upscaled_relperm"); std::string krw_filename = kr_filename + "_water"; std::string kro_filename = kr_filename + "_oil"; std::ofstream krw_out(krw_filename.c_str()); std::ofstream kro_out(kro_filename.c_str()); krw_out << "# Result from steady state upscaling" << std::endl; krw_out << "# Pressuredrop Sw Krxx Kryy Krzz" << std::endl; kro_out << "# Result from steady state upscaling" << std::endl; kro_out << "# Pressuredrop Sw Krxx Kryy Krzz" << std::endl; krw_out.precision(15); krw_out.setf(std::ios::scientific | std::ios::showpoint); kro_out.precision(15); kro_out.setf(std::ios::scientific | std::ios::showpoint); //#endif // Then, compute some upscaled relative permeabilities. int num_cells = upscaler.grid().size(0); int num_sats = saturations.size(); for (int i = 0; i < num_sats; ++i) { // Starting every computation with a trio of uniform profiles. std::vector<double> init_sat(num_cells, saturations[i]); const Opm::SparseTable<double>::row_type pdrops = all_pdrops[i]; int num_pdrops = pdrops.size(); for (int j = 0; j < num_pdrops; ++j) { double pdrop = pdrops[j]; std::pair<permtensor_t, permtensor_t> lambda = upscaler.upscaleSteadyState(flow_direction, init_sat, saturations[i], pdrop, upscaled_K); double usat = upscaler.lastSaturationUpscaled(); std::cout << "\n\nTensor of upscaled relperms for initial saturation " << saturations[i] << ", real steady-state saturation " << usat << " and pressure drop " << pdrop << ":\n\n[water]\n" << lambda.first << "\n[oil]\n" << lambda.second << std::endl; // Changing initial saturations for next pressure drop to equal the steady state of the last init_sat = upscaler.lastSaturationState(); writeRelPerm(krw_out, lambda.first , usat, pdrop); writeRelPerm(kro_out, lambda.second, usat, pdrop); } } }
static Evaluation liquidPressure(const Evaluation& /*temperature*/, const Evaluation& /*density*/) { OPM_THROW(std::logic_error, "The liquid pressure is undefined for incompressible fluids"); }
double IntegrationTest::integrateError(const std::vector<double>& timeVec1, const std::vector<double>& dataVec1, const std::vector<double>& timeVec2, const std::vector<double>& dataVec2){ // When the data corresponds to a rate the integration will become a Riemann // sum. This function calculates the Riemann sum of the error. The reason why // a Riemann sum is used is because of the way the data is written to file. // When a change occur (e.g. change of a rate), the data (value and time) is // written to file, THEN the change happens in the simulator, i.e., we will // notice the change at the next step. // // Keep in mind that the summary vector is NOT a continuous curve, only points // of data (time, value). We have to guess what happens between the data // points, we do this by saying: "There are no change, the only change happens // at the data points." As stated above, the value of this constant "height" of // the rectangle corresponds to the value of the last time step. Thus we have // to use the "right hand side value of the rectangle as height // // someDataVector[ivar] instead of someDataVector[ivar-1] // // (which intuition is saying is the correct value to use). if(timeVec1.size() != dataVec1.size() || timeVec2.size() != dataVec2.size() ){ OPM_THROW(std::runtime_error, "The size of the time vector does not match the size of the data vector."); } double errorSum = 0; double rightEdge, leftEdge, width; size_t i = 1; size_t j = 1; leftEdge = timeVec1[0]; while(i < timeVec1.size()){ if(j == timeVec2.size() ){ break; } if(timeVec1[i] == timeVec2[j]){ rightEdge = timeVec1[i]; width = rightEdge - leftEdge; double dev = std::fabs(dataVec1[i] - dataVec2[j]); errorSum += getRectangleArea(dev, width); leftEdge = rightEdge; i++; j++; continue; } if(timeVec1[i] < timeVec2[j]){ rightEdge = timeVec1[i]; width = rightEdge - leftEdge; double value = unitStep(dataVec2[j]); double dev = std::fabs(dataVec1[i]-value); errorSum += getRectangleArea(dev, width); leftEdge = rightEdge; i++; continue; } if(timeVec2[j] < timeVec1[i]){ rightEdge = timeVec2[j]; width = rightEdge - leftEdge; double value = unitStep(dataVec1[i]); double dev = std::fabs(dataVec2[j]-value); errorSum += getRectangleArea(dev, width); leftEdge = rightEdge; j++; continue; } } return errorSum; }
void SimulatorBase<Implementation>::computeRESV(const std::size_t step, const Wells* wells, const BlackoilState& x, WellState& xw) { typedef SimFIBODetails::WellMap WellMap; const std::vector<WellConstPtr>& w_ecl = eclipse_state_->getSchedule()->getWells(step); const WellMap& wmap = SimFIBODetails::mapWells(w_ecl); const std::vector<int>& resv_wells = SimFIBODetails::resvWells(wells, step, wmap); const std::size_t number_resv_wells = resv_wells.size(); std::size_t global_number_resv_wells = number_resv_wells; #if HAVE_MPI if ( solver_.parallelInformation().type() == typeid(ParallelISTLInformation) ) { const auto& info = boost::any_cast<const ParallelISTLInformation&>(solver_.parallelInformation()); global_number_resv_wells = info.communicator().sum(global_number_resv_wells); if ( global_number_resv_wells ) { // At least one process has resv wells. Therefore rate converter needs // to calculate averages over regions that might cross process // borders. This needs to be done by all processes and therefore // outside of the next if statement. rateConverter_.defineState(x, boost::any_cast<const ParallelISTLInformation&>(solver_.parallelInformation())); } } else #endif { if ( global_number_resv_wells ) { rateConverter_.defineState(x); } } if (! resv_wells.empty()) { const PhaseUsage& pu = props_.phaseUsage(); const std::vector<double>::size_type np = props_.numPhases(); std::vector<double> distr (np); std::vector<double> hrates(np); std::vector<double> prates(np); for (std::vector<int>::const_iterator rp = resv_wells.begin(), e = resv_wells.end(); rp != e; ++rp) { WellControls* ctrl = wells->ctrls[*rp]; const bool is_producer = wells->type[*rp] == PRODUCER; // RESV control mode, all wells { const int rctrl = SimFIBODetails::resv_control(ctrl); if (0 <= rctrl) { const std::vector<double>::size_type off = (*rp) * np; if (is_producer) { // Convert to positive rates to avoid issues // in coefficient calculations. std::transform(xw.wellRates().begin() + (off + 0*np), xw.wellRates().begin() + (off + 1*np), prates.begin(), std::negate<double>()); } else { std::copy(xw.wellRates().begin() + (off + 0*np), xw.wellRates().begin() + (off + 1*np), prates.begin()); } const int fipreg = 0; // Hack. Ignore FIP regions. rateConverter_.calcCoeff(prates, fipreg, distr); well_controls_iset_distr(ctrl, rctrl, & distr[0]); } } // RESV control, WCONHIST wells. A bit of duplicate // work, regrettably. if (is_producer && wells->name[*rp] != 0) { WellMap::const_iterator i = wmap.find(wells->name[*rp]); if (i != wmap.end()) { WellConstPtr wp = i->second; const WellProductionProperties& p = wp->getProductionProperties(step); if (! p.predictionMode) { // History matching (WCONHIST/RESV) SimFIBODetails::historyRates(pu, p, hrates); const int fipreg = 0; // Hack. Ignore FIP regions. rateConverter_.calcCoeff(hrates, fipreg, distr); // WCONHIST/RESV target is sum of all // observed phase rates translated to // reservoir conditions. Recall sign // convention: Negative for producers. const double target = - std::inner_product(distr.begin(), distr.end(), hrates.begin(), 0.0); well_controls_clear(ctrl); well_controls_assert_number_of_phases(ctrl, int(np)); static const double invalid_alq = -std::numeric_limits<double>::max(); static const int invalid_vfp = -std::numeric_limits<int>::max(); const int ok_resv = well_controls_add_new(RESERVOIR_RATE, target, invalid_alq, invalid_vfp, & distr[0], ctrl); // For WCONHIST the BHP limit is set to 1 atm. // or a value specified using WELTARG double bhp_limit = (p.BHPLimit > 0) ? p.BHPLimit : unit::convert::from(1.0, unit::atm); const int ok_bhp = well_controls_add_new(BHP, bhp_limit, invalid_alq, invalid_vfp, NULL, ctrl); if (ok_resv != 0 && ok_bhp != 0) { xw.currentControls()[*rp] = 0; well_controls_set_current(ctrl, 0); } } } } } } if( wells ) { for (int w = 0, nw = wells->number_of_wells; w < nw; ++w) { WellControls* ctrl = wells->ctrls[w]; const bool is_producer = wells->type[w] == PRODUCER; if (!is_producer && wells->name[w] != 0) { WellMap::const_iterator i = wmap.find(wells->name[w]); if (i != wmap.end()) { WellConstPtr wp = i->second; const WellInjectionProperties& injector = wp->getInjectionProperties(step); if (!injector.predictionMode) { //History matching WCONINJEH static const double invalid_alq = -std::numeric_limits<double>::max(); static const int invalid_vfp = -std::numeric_limits<int>::max(); // For WCONINJEH the BHP limit is set to a large number // or a value specified using WELTARG double bhp_limit = (injector.BHPLimit > 0) ? injector.BHPLimit : std::numeric_limits<double>::max(); const int ok_bhp = well_controls_add_new(BHP, bhp_limit, invalid_alq, invalid_vfp, NULL, ctrl); if (!ok_bhp) { OPM_THROW(std::runtime_error, "Failed to add well control."); } } } } } } }
inline void distributeGridAndData( Dune::CpGrid& grid, Opm::DeckConstPtr deck, EclipseStateConstPtr eclipseState, BlackoilState& state, BlackoilPropsAdFromDeck& properties, DerivedGeology& geology, std::shared_ptr<BlackoilPropsAdFromDeck::MaterialLawManager>& material_law_manager, std::vector<double>& threshold_pressures, boost::any& parallelInformation, const bool useLocalPerm) { Dune::CpGrid global_grid ( grid ); global_grid.switchToGlobalView(); // distribute the grid and switch to the distributed view grid.loadBalance(eclipseState, geology.transmissibility().data()); grid.switchToDistributedView(); std::vector<int> compressedToCartesianIdx; Opm::createGlobalCellArray(grid, compressedToCartesianIdx); typedef BlackoilPropsAdFromDeck::MaterialLawManager MaterialLawManager; auto distributed_material_law_manager = std::make_shared<MaterialLawManager>(); distributed_material_law_manager->initFromDeck(deck, eclipseState, compressedToCartesianIdx); // copy the values from the global to the local MaterialLawManager // We should actually communicate these to be future proof. But that is // really, really cumbersome for the underlying vector<shared_ptr> // where the classes pointed to even have more shared_ptr stored in them. typedef Dune::CpGrid::ParallelIndexSet IndexSet; const IndexSet& local_indices = grid.getCellIndexSet(); for ( auto index : local_indices ) { distributed_material_law_manager->materialLawParamsPointerReferenceHack(index.local()) = material_law_manager->materialLawParamsPointerReferenceHack(index.global()); distributed_material_law_manager->oilWaterScaledEpsInfoDrainagePointerReferenceHack(index.local()) = material_law_manager->oilWaterScaledEpsInfoDrainagePointerReferenceHack(index.global()); } BlackoilPropsAdFromDeck distributed_props(properties, distributed_material_law_manager, grid.numCells()); BlackoilState distributed_state(grid.numCells(), grid.numFaces(), state.numPhases()); BlackoilStateDataHandle state_handle(global_grid, grid, state, distributed_state); BlackoilPropsDataHandle props_handle(properties, distributed_props); grid.scatterData(state_handle); grid.scatterData(props_handle); // Create a distributed Geology. Some values will be updated using communication // below DerivedGeology distributed_geology(grid, distributed_props, eclipseState, useLocalPerm, geology.gravity()); GeologyDataHandle geo_handle(global_grid, grid, geology, distributed_geology); grid.scatterData(geo_handle); std::vector<double> distributed_pressures; if( !threshold_pressures.empty() ) // Might be empty if not specified { if( threshold_pressures.size() != static_cast<std::size_t>(UgGridHelpers::numFaces(global_grid)) ) { OPM_THROW(std::runtime_error, "NNCs not yet supported for parallel runs. " << UgGridHelpers::numFaces(grid) << " faces but " << threshold_pressures.size()<<" threshold pressure values"); } distributed_pressures.resize(UgGridHelpers::numFaces(grid)); ThresholdPressureDataHandle press_handle(global_grid, grid, threshold_pressures, distributed_pressures); grid.scatterData(press_handle); } // copy states properties = distributed_props; geology = distributed_geology; state = distributed_state; material_law_manager = distributed_material_law_manager; threshold_pressures = distributed_pressures; extractParallelGridInformationToISTL(grid, parallelInformation); }
SimulatorReport SimulatorCompressiblePolymer::Impl::run(SimulatorTimer& timer, PolymerBlackoilState& state, WellState& well_state) { std::vector<double> transport_src(grid_.number_of_cells); std::vector<double> polymer_inflow_c(grid_.number_of_cells); // Initialisation. std::vector<double> initial_pressure; std::vector<double> porevol; if (rock_comp_props_ && rock_comp_props_->isActive()) { computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol); } else { computePorevolume(grid_, props_.porosity(), porevol); } const double tot_porevol_init = std::accumulate(porevol.begin(), porevol.end(), 0.0); std::vector<double> initial_porevol = porevol; // Main simulation loop. Opm::time::StopWatch pressure_timer; double ptime = 0.0; Opm::time::StopWatch transport_timer; double ttime = 0.0; Opm::time::StopWatch total_timer; total_timer.start(); double init_surfvol[2] = { 0.0 }; double inplace_surfvol[2] = { 0.0 }; double polymass = computePolymerMass(porevol, state.saturation(), state.concentration(), poly_props_.deadPoreVol()); double polymass_adsorbed = computePolymerAdsorbed(grid_, props_, poly_props_, state, rock_comp_props_); double init_polymass = polymass + polymass_adsorbed; double tot_injected[2] = { 0.0 }; double tot_produced[2] = { 0.0 }; double tot_polyinj = 0.0; double tot_polyprod = 0.0; Opm::computeSaturatedVol(porevol, state.surfacevol(), init_surfvol); Opm::Watercut watercut; watercut.push(0.0, 0.0, 0.0); Opm::WellReport wellreport; std::vector<double> fractional_flows; std::vector<double> well_resflows_phase; if (wells_) { well_resflows_phase.resize((wells_->number_of_phases)*(wells_->number_of_wells), 0.0); wellreport.push(props_, *wells_, state.pressure(), state.surfacevol(), state.saturation(), 0.0, well_state.bhp(), well_state.perfRates()); } // Report timestep and (optionally) write state to disk. timer.report(std::cout); if (output_ && (timer.currentStepNum() % output_interval_ == 0)) { if (output_vtk_) { outputStateVtk(grid_, state, timer.currentStepNum(), output_dir_); } outputStateMatlab(grid_, state, timer.currentStepNum(), output_dir_); } initial_pressure = state.pressure(); // Solve pressure equation. if (check_well_controls_) { computeFractionalFlow(props_, poly_props_, allcells_, state.pressure(), state.temperature(), state.surfacevol(), state.saturation(), state.concentration(), state.maxconcentration(), fractional_flows); wells_manager_.applyExplicitReinjectionControls(well_resflows_phase, well_resflows_phase); } bool well_control_passed = !check_well_controls_; int well_control_iteration = 0; do { // Run solver pressure_timer.start(); psolver_.solve(timer.currentStepLength(), state, well_state); // Renormalize pressure if both fluids and rock are // incompressible, and there are no pressure // conditions (bcs or wells). It is deemed sufficient // for now to renormalize using geometric volume // instead of pore volume. if (psolver_.singularPressure()) { // Compute average pressures of previous and last // step, and total volume. double av_prev_press = 0.0; double av_press = 0.0; double tot_vol = 0.0; const int num_cells = grid_.number_of_cells; for (int cell = 0; cell < num_cells; ++cell) { av_prev_press += initial_pressure[cell]*grid_.cell_volumes[cell]; av_press += state.pressure()[cell]*grid_.cell_volumes[cell]; tot_vol += grid_.cell_volumes[cell]; } // Renormalization constant const double ren_const = (av_prev_press - av_press)/tot_vol; for (int cell = 0; cell < num_cells; ++cell) { state.pressure()[cell] += ren_const; } const int num_wells = (wells_ == NULL) ? 0 : wells_->number_of_wells; for (int well = 0; well < num_wells; ++well) { well_state.bhp()[well] += ren_const; } } // Stop timer and report pressure_timer.stop(); double pt = pressure_timer.secsSinceStart(); std::cout << "Pressure solver took: " << pt << " seconds." << std::endl; ptime += pt; // Optionally, check if well controls are satisfied. if (check_well_controls_) { Opm::computePhaseFlowRatesPerWell(*wells_, well_state.perfRates(), fractional_flows, well_resflows_phase); std::cout << "Checking well conditions." << std::endl; // For testing we set surface := reservoir well_control_passed = wells_manager_.conditionsMet(well_state.bhp(), well_resflows_phase, well_resflows_phase); ++well_control_iteration; if (!well_control_passed && well_control_iteration > max_well_control_iterations_) { OPM_THROW(std::runtime_error, "Could not satisfy well conditions in " << max_well_control_iterations_ << " tries."); } if (!well_control_passed) { std::cout << "Well controls not passed, solving again." << std::endl; } else { std::cout << "Well conditions met." << std::endl; } } } while (!well_control_passed); // Update pore volumes if rock is compressible. if (rock_comp_props_ && rock_comp_props_->isActive()) { initial_porevol = porevol; computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol); } // Process transport sources (to include bdy terms and well flows). Opm::computeTransportSource(props_, wells_, well_state, transport_src); // Find inflow rate. const double current_time = timer.simulationTimeElapsed(); double stepsize = timer.currentStepLength(); polymer_inflow_.getInflowValues(current_time, current_time + stepsize, polymer_inflow_c); // Solve transport. transport_timer.start(); if (num_transport_substeps_ != 1) { stepsize /= double(num_transport_substeps_); std::cout << "Making " << num_transport_substeps_ << " transport substeps." << std::endl; } double injected[2] = { 0.0 }; double produced[2] = { 0.0 }; double polyinj = 0.0; double polyprod = 0.0; for (int tr_substep = 0; tr_substep < num_transport_substeps_; ++tr_substep) { tsolver_.solve(&state.faceflux()[0], initial_pressure, state.pressure(), state.temperature(), &initial_porevol[0], &porevol[0], &transport_src[0], &polymer_inflow_c[0], stepsize, state.saturation(), state.surfacevol(), state.concentration(), state.maxconcentration()); double substep_injected[2] = { 0.0 }; double substep_produced[2] = { 0.0 }; double substep_polyinj = 0.0; double substep_polyprod = 0.0; Opm::computeInjectedProduced(props_, poly_props_, state, transport_src, polymer_inflow_c, stepsize, substep_injected, substep_produced, substep_polyinj, substep_polyprod); injected[0] += substep_injected[0]; injected[1] += substep_injected[1]; produced[0] += substep_produced[0]; produced[1] += substep_produced[1]; polyinj += substep_polyinj; polyprod += substep_polyprod; if (gravity_ != 0 && use_segregation_split_) { tsolver_.solveGravity(columns_, stepsize, state.saturation(), state.surfacevol(), state.concentration(), state.maxconcentration()); } } transport_timer.stop(); double tt = transport_timer.secsSinceStart(); std::cout << "Transport solver took: " << tt << " seconds." << std::endl; ttime += tt; // Report volume balances. Opm::computeSaturatedVol(porevol, state.surfacevol(), inplace_surfvol); polymass = Opm::computePolymerMass(porevol, state.saturation(), state.concentration(), poly_props_.deadPoreVol()); polymass_adsorbed = Opm::computePolymerAdsorbed(grid_, props_, poly_props_, state, rock_comp_props_); tot_injected[0] += injected[0]; tot_injected[1] += injected[1]; tot_produced[0] += produced[0]; tot_produced[1] += produced[1]; tot_polyinj += polyinj; tot_polyprod += polyprod; std::cout.precision(5); const int width = 18; std::cout << "\nMass balance: " " water(surfvol) oil(surfvol) polymer(kg)\n"; std::cout << " In-place: " << std::setw(width) << inplace_surfvol[0] << std::setw(width) << inplace_surfvol[1] << std::setw(width) << polymass << std::endl; std::cout << " Adsorbed: " << std::setw(width) << 0.0 << std::setw(width) << 0.0 << std::setw(width) << polymass_adsorbed << std::endl; std::cout << " Injected: " << std::setw(width) << injected[0] << std::setw(width) << injected[1] << std::setw(width) << polyinj << std::endl; std::cout << " Produced: " << std::setw(width) << produced[0] << std::setw(width) << produced[1] << std::setw(width) << polyprod << std::endl; std::cout << " Total inj: " << std::setw(width) << tot_injected[0] << std::setw(width) << tot_injected[1] << std::setw(width) << tot_polyinj << std::endl; std::cout << " Total prod: " << std::setw(width) << tot_produced[0] << std::setw(width) << tot_produced[1] << std::setw(width) << tot_polyprod << std::endl; const double balance[3] = { init_surfvol[0] - inplace_surfvol[0] - tot_produced[0] + tot_injected[0], init_surfvol[1] - inplace_surfvol[1] - tot_produced[1] + tot_injected[1], init_polymass - polymass - tot_polyprod + tot_polyinj - polymass_adsorbed }; std::cout << " Initial - inplace + inj - prod: " << std::setw(width) << balance[0] << std::setw(width) << balance[1] << std::setw(width) << balance[2] << std::endl; std::cout << " Relative mass error: " << std::setw(width) << balance[0]/(init_surfvol[0] + tot_injected[0]) << std::setw(width) << balance[1]/(init_surfvol[1] + tot_injected[1]) << std::setw(width) << balance[2]/(init_polymass + tot_polyinj) << std::endl; std::cout.precision(8); watercut.push(timer.simulationTimeElapsed() + timer.currentStepLength(), produced[0]/(produced[0] + produced[1]), tot_produced[0]/tot_porevol_init); if (wells_) { wellreport.push(props_, *wells_, state.pressure(), state.surfacevol(), state.saturation(), timer.simulationTimeElapsed() + timer.currentStepLength(), well_state.bhp(), well_state.perfRates()); } if (output_) { if (output_vtk_) { outputStateVtk(grid_, state, timer.currentStepNum(), output_dir_); } outputStateMatlab(grid_, state, timer.currentStepNum(), output_dir_); outputWaterCut(watercut, output_dir_); if (wells_) { outputWellReport(wellreport, output_dir_); } } total_timer.stop(); SimulatorReport report; report.pressure_time = ptime; report.transport_time = ttime; report.total_time = total_timer.secsSinceStart(); return report; }
static void solve(FluidState &fluidState, const typename MaterialLaw::Params &matParams, typename FluidSystem::template ParameterCache<typename FluidState::Scalar>& paramCache, const Dune::FieldVector<typename FluidState::Scalar, numComponents>& globalMolarities, Scalar tolerance = -1) { typedef typename FluidState::Scalar InputEval; ///////////////////////// // Check if all fluid phases are incompressible ///////////////////////// bool allIncompressible = true; for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { if (FluidSystem::isCompressible(phaseIdx)) { allIncompressible = false; break; } } if (allIncompressible) { // yes, all fluid phases are incompressible. in this case the flash solver // can only determine the saturations, not the pressures. (but this // determination is much simpler than a full flash calculation.) paramCache.updateAll(fluidState); solveAllIncompressible_(fluidState, paramCache, globalMolarities); return; } typedef Dune::FieldMatrix<InputEval, numEq, numEq> Matrix; typedef Dune::FieldVector<InputEval, numEq> Vector; typedef Opm::LocalAd::Evaluation<InputEval, numEq> FlashEval; typedef Dune::FieldVector<FlashEval, numEq> FlashDefectVector; typedef Opm::ImmiscibleFluidState<FlashEval, FluidSystem> FlashFluidState; Dune::FMatrixPrecision<InputEval>::set_singular_limit(1e-35); if (tolerance <= 0) tolerance = std::min<Scalar>(1e-5, 1e8*std::numeric_limits<Scalar>::epsilon()); typename FluidSystem::template ParameterCache<FlashEval> flashParamCache; flashParamCache.assignPersistentData(paramCache); ///////////////////////// // Newton method ///////////////////////// // Jacobian matrix Matrix J; // solution, i.e. phase composition Vector deltaX; // right hand side Vector b; Valgrind::SetUndefined(J); Valgrind::SetUndefined(deltaX); Valgrind::SetUndefined(b); FlashFluidState flashFluidState; assignFlashFluidState_<MaterialLaw>(fluidState, flashFluidState, matParams, flashParamCache); // copy the global molarities to a vector of evaluations. Remember that the // global molarities are constants. (but we need to copy them to a vector of // FlashEvals anyway in order to avoid getting into hell's kitchen.) Dune::FieldVector<FlashEval, numComponents> flashGlobalMolarities; for (unsigned compIdx = 0; compIdx < numComponents; ++ compIdx) flashGlobalMolarities[compIdx] = globalMolarities[compIdx]; FlashDefectVector defect; const unsigned nMax = 50; // <- maximum number of newton iterations for (unsigned nIdx = 0; nIdx < nMax; ++nIdx) { // calculate Jacobian matrix and right hand side evalDefect_(defect, flashFluidState, flashGlobalMolarities); Valgrind::CheckDefined(defect); // create field matrices and vectors out of the evaluation vector to solve // the linear system of equations. for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { for (unsigned pvIdx = 0; pvIdx < numEq; ++ pvIdx) J[eqIdx][pvIdx] = defect[eqIdx].derivatives[pvIdx]; b[eqIdx] = defect[eqIdx].value; } Valgrind::CheckDefined(J); Valgrind::CheckDefined(b); // Solve J*x = b deltaX = 0; try { J.solve(deltaX, b); } catch (Dune::FMatrixError e) { throw Opm::NumericalProblem(e.what()); } Valgrind::CheckDefined(deltaX); // update the fluid quantities. Scalar relError = update_<MaterialLaw>(flashFluidState, flashParamCache, matParams, deltaX); if (relError < tolerance) { assignOutputFluidState_(flashFluidState, fluidState); return; } } OPM_THROW(Opm::NumericalProblem, "ImmiscibleFlash solver failed: " "{c_alpha^kappa} = {" << globalMolarities << "}, " << "T = " << fluidState.temperature(/*phaseIdx=*/0)); }
/// set the tables which specify the temperature dependence of the oil viscosity void initFromDeck(std::shared_ptr<const PvtInterface> isothermalPvt, const Opm::Deck& deck, const Opm::EclipseState& eclipseState) { isothermalPvt_ = isothermalPvt; int numRegions; auto tables = eclipseState->getTableManager(); if (deck->hasKeyword("PVTO")) numRegions = tables->getPvtoTables().size(); else if (deck->hasKeyword("PVDO")) numRegions = tables->getPvdoTables().size(); else if (deck->hasKeyword("PVCDO")) numRegions = deck->getKeyword("PVCDO").size(); else OPM_THROW(std::runtime_error, "Oil phase was not initialized using a known way"); // viscosity if (deck->hasKeyword("VISCREF")) { oilvisctTables_ = &tables->getOilvisctTables(); const auto& viscrefKeyword = deck->getKeyword("VISCREF"); assert(int(oilvisctTables_->size()) == numRegions); assert(int(viscrefKeyword.size()) == numRegions); viscrefPress_.resize(numRegions); viscrefRs_.resize(numRegions); muRef_.resize(numRegions); for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) { const auto& viscrefRecord = viscrefKeyword.getRecord(regionIdx); viscrefPress_[regionIdx] = viscrefRecord.getItem("REFERENCE_PRESSURE").getSIDouble(0); viscrefRs_[regionIdx] = viscrefRecord.getItem("REFERENCE_RS").getSIDouble(0); // temperature used to calculate the reference viscosity [K]. the // value does not really matter if the underlying PVT object really // is isothermal... double Tref = 273.15 + 20; // compute the reference viscosity using the isothermal PVT object. double tmp1, tmp2; isothermalPvt_->mu(1, ®ionIdx, &viscrefPress_[regionIdx], &Tref, &viscrefRs_[regionIdx], &muRef_[regionIdx], &tmp1, &tmp2); } } // quantities required for density. note that we just always use the values // for the first EOS. (since EOS != PVT region.) tref_ = 0.0; if (deck->hasKeyword("THERMEX1")) { oilCompIdx_ = deck->getKeyword("OCOMPIDX").getRecord(0).getItem("OIL_COMPONENT_INDEX").get< int >(0) - 1; // always use the values of the first EOS tref_ = deck->getKeyword("TREF").getRecord(0).getItem("TEMPERATURE").getSIDouble(oilCompIdx_); pref_ = deck->getKeyword("PREF").getRecord(0).getItem("PRESSURE").getSIDouble(oilCompIdx_); cref_ = deck->getKeyword("CREF").getRecord(0).getItem("COMPRESSIBILITY").getSIDouble(oilCompIdx_); thermex1_ = deck->getKeyword("THERMEX1").getRecord(0).getItem("EXPANSION_COEFF").getSIDouble(oilCompIdx_); } }
void TransportSolverTwophaseReorder::solveMultiCell(const int num_cells, const int* cells) { // std::ofstream os("dump"); // std::copy(cells, cells + num_cells, std::ostream_iterator<double>(os, "\n")); // Experiment: try a breath-first search to build a more suitable ordering. // Verdict: failed to improve #iterations. // { // std::vector<int> pos(grid_.number_of_cells, -1); // for (int i = 0; i < num_cells; ++i) { // const int cell = cells[i]; // pos[cell] = i; // } // std::vector<int> done_pos(num_cells, 0); // std::vector<int> upstream_pos; // std::vector<int> new_pos; // upstream_pos.push_back(0); // done_pos[0] = 1; // int current = 0; // while (int(new_pos.size()) < num_cells) { // const int i = upstream_pos[current++]; // new_pos.push_back(i); // const int cell = cells[i]; // for (int j = ia_[cell]; j < ia_[cell+1]; ++j) { // const int opos = pos[ja_[j]]; // if (!done_pos[opos]) { // upstream_pos.push_back(opos); // done_pos[opos] = 1; // } // } // } // std::reverse(new_pos.begin(), new_pos.end()); // std::copy(new_pos.begin(), new_pos.end(), const_cast<int*>(cells)); // } // Experiment: try a random ordering. // Verdict: amazingly, reduced #iterations by approx. 25%! // int* c = const_cast<int*>(cells); // std::random_shuffle(c, c + num_cells); // Experiment: compute topological tof from first cell. // Verdict: maybe useful, not tried to exploit it yet. // std::vector<int> tof; // TofComputer comp(grid_.number_of_cells, &ia_[0], &ja_[0], cells[0], tof); // std::ofstream tofdump("tofdump"); // std::copy(tof.begin(), tof.end(), std::ostream_iterator<double>(tofdump, "\n")); // Experiment: implement a metric measuring badness of ordering // as average distance in (cyclic) ordering from // upstream neighbours. // Verdict: does not seem to predict #iterations very well, if at all. // std::vector<int> pos(grid_.number_of_cells, -1); // for (int i = 0; i < num_cells; ++i) { // const int cell = cells[i]; // pos[cell] = i; // } // double diffsum = 0; // for (int i = 0; i < num_cells; ++i) { // const int cell = cells[i]; // int num_upstream = 0; // int loc_diffsum = 0; // for (int j = ia_[cell]; j < ia_[cell+1]; ++j) { // const int opos = pos[ja_[j]]; // if (opos == -1) { // std::cout << "Hmmm" << std::endl; // continue; // } // ++num_upstream; // const int diff = (i - opos + num_cells) % num_cells; // loc_diffsum += diff; // } // diffsum += double(loc_diffsum)/double(num_upstream); // } // std::cout << "Average distance from upstream neighbours: " << diffsum/double(num_cells) // << std::endl; #ifdef EXPERIMENT_GAUSS_SEIDEL // Experiment: when a cell changes more than the tolerance, // mark all downwind cells as needing updates. After // computing a single update in each cell, use marks // to guide further updating. Clear mark in cell when // its solution gets updated. // Verdict: this is a good one! Approx. halved total time. std::vector<int> needs_update(num_cells, 1); // This one also needs the mapping from all cells to // the strongly connected subset to filter out connections std::vector<int> pos(grid_.number_of_cells, -1); for (int i = 0; i < num_cells; ++i) { const int cell = cells[i]; pos[cell] = i; } // Note: partially copied from below. const double tol = 1e-9; const int max_iters = 300; // Must store s0 before we start. std::vector<double> s0(num_cells); // Must set initial fractional flows before we start. // Also, we compute the # of upstream neighbours. // std::vector<int> num_upstream(num_cells); for (int i = 0; i < num_cells; ++i) { const int cell = cells[i]; fractionalflow_[cell] = fracFlow(saturation_[cell], cell); s0[i] = saturation_[cell]; // num_upstream[i] = ia_upw_[cell + 1] - ia_upw_[cell]; } // Solve once in each cell. // std::vector<int> fully_marked_stack; // fully_marked_stack.reserve(num_cells); int num_iters = 0; int update_count = 0; // Change name/meaning to cells_updated? do { update_count = 0; // Must reset count for every iteration. for (int i = 0; i < num_cells; ++i) { // while (!fully_marked_stack.empty()) { // // std::cout << "# fully marked cells = " << fully_marked_stack.size() << std::endl; // const int fully_marked_ci = fully_marked_stack.back(); // fully_marked_stack.pop_back(); // ++update_count; // const int cell = cells[fully_marked_ci]; // const double old_s = saturation_[cell]; // saturation_[cell] = s0[fully_marked_ci]; // solveSingleCell(cell); // const double s_change = std::fabs(saturation_[cell] - old_s); // if (s_change > tol) { // // Mark downwind cells. // for (int j = ia_downw_[cell]; j < ia_downw_[cell+1]; ++j) { // const int downwind_cell = ja_downw_[j]; // int ci = pos[downwind_cell]; // ++needs_update[ci]; // if (needs_update[ci] == num_upstream[ci]) { // fully_marked_stack.push_back(ci); // } // } // } // // Unmark this cell. // needs_update[fully_marked_ci] = 0; // } if (!needs_update[i]) { continue; } ++update_count; const int cell = cells[i]; const double old_s = saturation_[cell]; saturation_[cell] = s0[i]; solveSingleCell(cell); const double s_change = std::fabs(saturation_[cell] - old_s); if (s_change > tol) { // Mark downwind cells. for (int j = ia_downw_[cell]; j < ia_downw_[cell+1]; ++j) { const int downwind_cell = ja_downw_[j]; int ci = pos[downwind_cell]; if (ci != -1) { needs_update[ci] = 1; } // ++needs_update[ci]; // if (needs_update[ci] == num_upstream[ci]) { // fully_marked_stack.push_back(ci); // } } } // Unmark this cell. needs_update[i] = 0; } // std::cout << "Iter = " << num_iters << " update_count = " << update_count // << " # marked cells = " // << std::accumulate(needs_update.begin(), needs_update.end(), 0) << std::endl; } while (update_count > 0 && ++num_iters < max_iters); // Done with iterations, check if we succeeded. if (update_count > 0) { OPM_THROW(std::runtime_error, "In solveMultiCell(), we did not converge after " << num_iters << " iterations. Remaining update count = " << update_count); } std::cout << "Solved " << num_cells << " cell multicell problem in " << num_iters << " iterations." << std::endl; #else double max_s_change = 0.0; const double tol = 1e-9; const int max_iters = 300; int num_iters = 0; // Must store s0 before we start. std::vector<double> s0(num_cells); // Must set initial fractional flows before we start. for (int i = 0; i < num_cells; ++i) { const int cell = cells[i]; fractionalflow_[cell] = fracFlow(saturation_[cell], cell); s0[i] = saturation_[cell]; } do { max_s_change = 0.0; for (int i = 0; i < num_cells; ++i) { const int cell = cells[i]; const double old_s = saturation_[cell]; saturation_[cell] = s0[i]; solveSingleCell(cell); double s_change = std::fabs(saturation_[cell] - old_s); // std::cout << "cell = " << cell << " delta s = " << s_change << std::endl; if (max_s_change < s_change) { max_s_change = s_change; } } // std::cout << "Iter = " << num_iters << " max_s_change = " << max_s_change // << " in cell " << max_change_cell << std::endl; } while (max_s_change > tol && ++num_iters < max_iters); if (max_s_change > tol) { OPM_THROW(std::runtime_error, "In solveMultiCell(), we did not converge after " << num_iters << " iterations. Delta s = " << max_s_change); } std::cout << "Solved " << num_cells << " cell multicell problem in " << num_iters << " iterations." << std::endl; #endif // EXPERIMENT_GAUSS_SEIDEL }
static Scalar heatConductivity(const Params ¶ms, const FluidState &fluidState) { OPM_THROW(std::logic_error, "No heat conduction law specified!"); }
/*! * \brief The molar density of a fluid phase [mol/m^3] */ Scalar molarDensity(int /* phaseIdx */) const { OPM_THROW(std::logic_error, "Molar density is not provided by this fluid state"); }
static Evaluation liquidDiffCoeff(const Evaluation& temperature, const Evaluation& pressure) { OPM_THROW(std::runtime_error, "Not implemented: Binary liquid diffusion coefficients of CO2 and CH4"); }
/*! * \brief The molar volume of a fluid phase [m^3/mol] */ Scalar molarVolume(int /* phaseIdx */) const { OPM_THROW(std::logic_error, "Molar volume is not provided by this fluid state"); }
static Evaluation Sg(const Params& /* params */, const FluidState& /* fluidState */) { OPM_THROW(std::logic_error, "Not implemented: Sg()"); }
Scalar bringOilToSurface(FluidState &surfaceFluidState, Scalar alpha, const FluidState &reservoirFluidState, bool guessInitial) { enum { numPhases = FluidSystem::numPhases, waterPhaseIdx = FluidSystem::waterPhaseIdx, gasPhaseIdx = FluidSystem::gasPhaseIdx, oilPhaseIdx = FluidSystem::oilPhaseIdx, numComponents = FluidSystem::numComponents }; typedef Opm::NcpFlash<Scalar, FluidSystem> Flash; typedef Opm::ThreePhaseMaterialTraits<Scalar, waterPhaseIdx, oilPhaseIdx, gasPhaseIdx> MaterialTraits; typedef Opm::LinearMaterial<MaterialTraits> MaterialLaw; typedef typename MaterialLaw::Params MaterialLawParams; typedef Dune::FieldVector<Scalar, numComponents> ComponentVector; const Scalar refPressure = 1.0135e5; // [Pa] // set the parameters for the capillary pressure law MaterialLawParams matParams; for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { matParams.setPcMinSat(phaseIdx, 0.0); matParams.setPcMaxSat(phaseIdx, 0.0); } matParams.finalize(); // retieve the global volumetric component molarities surfaceFluidState.setTemperature(273.15 + 20); ComponentVector molarities; for (int compIdx = 0; compIdx < numComponents; ++ compIdx) molarities[compIdx] = reservoirFluidState.molarity(oilPhaseIdx, compIdx); if (guessInitial) { // we start at a fluid state with reservoir oil. for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { for (int compIdx = 0; compIdx < numComponents; ++ compIdx) { surfaceFluidState.setMoleFraction(phaseIdx, compIdx, reservoirFluidState.moleFraction(phaseIdx, compIdx)); } surfaceFluidState.setDensity(phaseIdx, reservoirFluidState.density(phaseIdx)); surfaceFluidState.setPressure(phaseIdx, reservoirFluidState.pressure(phaseIdx)); surfaceFluidState.setSaturation(phaseIdx, 0.0); } surfaceFluidState.setSaturation(oilPhaseIdx, 1.0); surfaceFluidState.setSaturation(gasPhaseIdx, 1.0 - surfaceFluidState.saturation(oilPhaseIdx)); } typename FluidSystem::ParameterCache paramCache; paramCache.updateAll(surfaceFluidState); // increase volume until we are at surface pressure. use the // newton method for this ComponentVector tmpMolarities; for (int i = 0;; ++i) { if (i >= 20) OPM_THROW(Opm::NumericalIssue, "Newton method did not converge after 20 iterations"); // calculate the deviation from the standard pressure tmpMolarities = molarities; tmpMolarities /= alpha; Flash::template solve<MaterialLaw>(surfaceFluidState, paramCache, matParams, tmpMolarities); Scalar f = surfaceFluidState.pressure(gasPhaseIdx) - refPressure; // calculate the derivative of the deviation from the standard // pressure Scalar eps = alpha*1e-10; tmpMolarities = molarities; tmpMolarities /= alpha + eps; Flash::template solve<MaterialLaw>(surfaceFluidState, paramCache, matParams, tmpMolarities); Scalar fStar = surfaceFluidState.pressure(gasPhaseIdx) - refPressure; Scalar fPrime = (fStar - f)/eps; // newton update Scalar delta = f/fPrime; alpha -= delta; if (std::abs(delta) < std::abs(alpha)*1e-9) { break; } } // calculate the final result tmpMolarities = molarities; tmpMolarities /= alpha; Flash::template solve<MaterialLaw>(surfaceFluidState, paramCache, matParams, tmpMolarities); return alpha; }