mirror of
https://github.com/OPM/opm-simulators.git
synced 2026-10-26 15:40:09 -05:00
Merge pull request #6103 from jakobtorben/add_support_for_merging_well_cells_for_nldd_partitioning
Add support for merging well cells for NLDD partitioning
This commit is contained in:
8 files changed
+860
-94
No files matched your search
@@ -107,7 +107,26 @@ public:
|
||||
, rank_(model_.simulator().vanguard().grid().comm().rank())
|
||||
{
|
||||
// Create partitions.
|
||||
const auto& [partition_vector, num_domains] = this->partitionCells();
|
||||
const auto& [partition_vector_initial, num_domains_initial] = this->partitionCells();
|
||||
|
||||
int num_domains = num_domains_initial;
|
||||
std::vector<int> partition_vector = partition_vector_initial;
|
||||
|
||||
// Fix-up for an extreme case: Interior cells who do not have any on-rank
|
||||
// neighbours. Move all such cells into a single domain on this rank,
|
||||
// and mark the domain for skipping. For what it's worth, we've seen this
|
||||
// case occur in practice when testing on field cases.
|
||||
bool isolated_cells = false;
|
||||
for (auto& domainId : partition_vector) {
|
||||
if (domainId < 0) {
|
||||
domainId = num_domains;
|
||||
domains_to_skip_.push_back(domainId);
|
||||
isolated_cells = true;
|
||||
}
|
||||
}
|
||||
if (isolated_cells) {
|
||||
num_domains++;
|
||||
}
|
||||
|
||||
// Set nldd handler in main well model
|
||||
model.wellModel().setNlddAdapter(&wellModel_);
|
||||
@@ -222,6 +241,9 @@ public:
|
||||
for (const int domain_index : domain_order) {
|
||||
const auto& domain = domains_[domain_index];
|
||||
SimulatorReportSingle local_report;
|
||||
if (std::find(domains_to_skip_.begin(), domains_to_skip_.end(), domain.index) != domains_to_skip_.end()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
switch (model_.param().local_solve_approach_) {
|
||||
case DomainSolveApproach::Jacobi:
|
||||
@@ -340,23 +362,55 @@ public:
|
||||
return local_reports_accumulated_;
|
||||
}
|
||||
|
||||
/// Write the partition vector to a file in ResInsight compatible format for inspection
|
||||
/// and a partition file for each rank, that can be used as input for OPM.
|
||||
void writePartitions(const std::filesystem::path& odir) const
|
||||
{
|
||||
const auto& elementMapper = this->model_.simulator().model().elementMapper();
|
||||
const auto& cartMapper = this->model_.simulator().vanguard().cartesianIndexMapper();
|
||||
const auto& dims = cartMapper.cartesianDimensions();
|
||||
const auto total_size = dims[0] * dims[1] * dims[2];
|
||||
|
||||
const auto& grid = this->model_.simulator().vanguard().grid();
|
||||
const auto& comm = grid.comm();
|
||||
const auto nDigit = 1 + static_cast<int>(std::floor(std::log10(comm.size())));
|
||||
|
||||
std::ofstream pfile { odir / fmt::format("{1:0>{0}}", nDigit, comm.rank()) };
|
||||
// Create a full-sized vector initialized with -1 (indicating inactive cells)
|
||||
auto full_partition = std::vector<int>(total_size, -1);
|
||||
|
||||
// Fill active cell values for this rank
|
||||
const auto p = this->reconstitutePartitionVector();
|
||||
auto i = 0;
|
||||
for (const auto& cell : elements(grid.leafGridView(), Dune::Partitions::interior)) {
|
||||
full_partition[cartMapper.cartesianIndex(elementMapper.index(cell))] = p[i++];
|
||||
}
|
||||
|
||||
// Gather all partitions using max operation
|
||||
comm.max(full_partition.data(), full_partition.size());
|
||||
|
||||
// Only rank 0 writes the file
|
||||
if (this->rank_ == 0) {
|
||||
auto fname = odir / "ResInsight_compatible_partition.txt";
|
||||
std::ofstream resInsightFile { fname };
|
||||
|
||||
// Write header
|
||||
resInsightFile << "NLDD_DOM" << '\n';
|
||||
|
||||
// Write all cells, including inactive ones
|
||||
for (const auto& val : full_partition) {
|
||||
resInsightFile << val << '\n';
|
||||
}
|
||||
resInsightFile << "/" << '\n';
|
||||
}
|
||||
|
||||
const auto nDigit = 1 + static_cast<int>(std::floor(std::log10(comm.size())));
|
||||
auto partition_fname = odir / fmt::format("{1:0>{0}}", nDigit, comm.rank());
|
||||
std::ofstream pfile { partition_fname };
|
||||
|
||||
auto cell_index = 0;
|
||||
for (const auto& cell : elements(grid.leafGridView(), Dune::Partitions::interior)) {
|
||||
pfile << comm.rank() << ' '
|
||||
<< cartMapper.cartesianIndex(elementMapper.index(cell)) << ' '
|
||||
<< p[i++] << '\n';
|
||||
<< p[cell_index++] << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,7 +817,7 @@ private:
|
||||
} else if (model_.param().local_solve_approach_ == DomainSolveApproach::GaussSeidel) {
|
||||
// Calculate the measure used to order the domains.
|
||||
std::vector<Scalar> measure_per_domain(domains_.size());
|
||||
switch (model_.param().local_domain_ordering_) {
|
||||
switch (model_.param().local_domains_ordering_) {
|
||||
case DomainOrderingMeasure::AveragePressure: {
|
||||
// Use average pressures to order domains.
|
||||
for (const auto& domain : domains_) {
|
||||
@@ -802,7 +856,7 @@ private:
|
||||
}
|
||||
break;
|
||||
}
|
||||
} // end of switch (model_.param().local_domain_ordering_)
|
||||
} // end of switch (model_.param().local_domains_ordering_)
|
||||
|
||||
// Sort by largest measure, keeping index order if equal.
|
||||
const auto& m = measure_per_domain;
|
||||
@@ -937,7 +991,7 @@ private:
|
||||
|
||||
auto zoltan_ctrl = ZoltanPartitioningControl<Element>{};
|
||||
|
||||
zoltan_ctrl.domain_imbalance = param.local_domain_partition_imbalance_;
|
||||
zoltan_ctrl.domain_imbalance = param.local_domains_partition_imbalance_;
|
||||
|
||||
zoltan_ctrl.index =
|
||||
[elementMapper = &this->model_.simulator().model().elementMapper()]
|
||||
@@ -954,7 +1008,7 @@ private:
|
||||
};
|
||||
|
||||
// Forming the list of wells is expensive, so do this only if needed.
|
||||
const auto need_wells = param.local_domain_partition_method_ == "zoltan";
|
||||
const auto need_wells = param.local_domains_partition_method_ == "zoltan";
|
||||
|
||||
const auto wells = need_wells
|
||||
? this->model_.simulator().vanguard().schedule().getWellsatEnd()
|
||||
@@ -969,10 +1023,10 @@ private:
|
||||
const int num_domains = (param.num_local_domains_ > 0)
|
||||
? param.num_local_domains_
|
||||
: detail::countGlobalCells(grid) / default_cells_per_domain;
|
||||
|
||||
return ::Opm::partitionCells(param.local_domain_partition_method_,
|
||||
return ::Opm::partitionCells(param.local_domains_partition_method_,
|
||||
num_domains, grid.leafGridView(), wells,
|
||||
possibleFutureConnectionSet, zoltan_ctrl);
|
||||
possibleFutureConnectionSet, zoltan_ctrl,
|
||||
param.local_domains_partition_well_neighbor_levels_);
|
||||
}
|
||||
|
||||
std::vector<int> reconstitutePartitionVector() const
|
||||
@@ -1008,6 +1062,7 @@ private:
|
||||
std::vector<Domain> domains_; //!< Vector of subdomains
|
||||
std::vector<std::unique_ptr<Mat>> domain_matrices_; //!< Vector of matrix operator for each subdomain
|
||||
std::vector<ISTLSolverType> domain_linsolvers_; //!< Vector of linear solvers for each domain
|
||||
std::vector<int> domains_to_skip_; //!< Vector of domains to skip
|
||||
SimulatorReportSingle local_reports_accumulated_; //!< Accumulated convergence report for subdomain solvers
|
||||
int rank_ = 0; //!< MPI rank of this process
|
||||
};
|
||||
|
||||
@@ -93,15 +93,16 @@ BlackoilModelParameters<Scalar>::BlackoilModelParameters()
|
||||
local_tolerance_scaling_cnv_ = Parameters::Get<Parameters::LocalToleranceScalingCnv<Scalar>>();
|
||||
nldd_num_initial_newton_iter_ = Parameters::Get<Parameters::NlddNumInitialNewtonIter>();
|
||||
num_local_domains_ = Parameters::Get<Parameters::NumLocalDomains>();
|
||||
local_domain_partition_imbalance_ = std::max(Scalar{1.0}, Parameters::Get<Parameters::LocalDomainsPartitioningImbalance<Scalar>>());
|
||||
local_domain_partition_method_ = Parameters::Get<Parameters::LocalDomainsPartitioningMethod>();
|
||||
local_domains_partition_imbalance_ = std::max(Scalar{1.0}, Parameters::Get<Parameters::LocalDomainsPartitioningImbalance<Scalar>>());
|
||||
local_domains_partition_method_ = Parameters::Get<Parameters::LocalDomainsPartitioningMethod>();
|
||||
local_domains_partition_well_neighbor_levels_ = Parameters::Get<Parameters::LocalDomainsPartitionWellNeighborLevels>();
|
||||
deck_file_name_ = Parameters::Get<Parameters::EclDeckFileName>();
|
||||
network_max_strict_outer_iterations_ = Parameters::Get<Parameters::NetworkMaxStrictOuterIterations>();
|
||||
network_max_outer_iterations_ = Parameters::Get<Parameters::NetworkMaxOuterIterations>();
|
||||
network_max_sub_iterations_ = Parameters::Get<Parameters::NetworkMaxSubIterations>();
|
||||
network_pressure_update_damping_factor_ = Parameters::Get<Parameters::NetworkPressureUpdateDampingFactor<Scalar>>();
|
||||
network_max_pressure_update_in_bars_ = Parameters::Get<Parameters::NetworkMaxPressureUpdateInBars<Scalar>>();
|
||||
local_domain_ordering_ = domainOrderingMeasureFromString(Parameters::Get<Parameters::LocalDomainsOrderingMeasure>());
|
||||
local_domains_ordering_ = domainOrderingMeasureFromString(Parameters::Get<Parameters::LocalDomainsOrderingMeasure>());
|
||||
write_partitions_ = Parameters::Get<Parameters::DebugEmitCellPartition>();
|
||||
|
||||
monitor_params_.enabled_ = Parameters::Get<Parameters::ConvergenceMonitoring>();
|
||||
@@ -253,6 +254,8 @@ void BlackoilModelParameters<Scalar>::registerParameters()
|
||||
"'zoltan', "
|
||||
"'simple', "
|
||||
"and the name of a partition file ending with '.partition'.");
|
||||
Parameters::Register<Parameters::LocalDomainsPartitionWellNeighborLevels>
|
||||
("Number of neighbor levels around wells to include in the same domain during NLDD partitioning");
|
||||
Parameters::Register<Parameters::LocalDomainsOrderingMeasure>
|
||||
("Subdomain ordering measure. Allowed values are "
|
||||
"'maxpressure', "
|
||||
|
||||
@@ -152,6 +152,7 @@ template<class Scalar>
|
||||
struct LocalDomainsPartitioningImbalance { static constexpr Scalar value = 1.03; };
|
||||
|
||||
struct LocalDomainsPartitioningMethod { static constexpr auto value = "zoltan"; };
|
||||
struct LocalDomainsPartitionWellNeighborLevels { static constexpr int value = 1; };
|
||||
struct LocalDomainsOrderingMeasure { static constexpr auto value = "maxpressure"; };
|
||||
|
||||
struct ConvergenceMonitoring { static constexpr bool value = false; };
|
||||
@@ -159,6 +160,7 @@ struct ConvergenceMonitoringCutOff { static constexpr int value = 6; };
|
||||
template<class Scalar>
|
||||
struct ConvergenceMonitoringDecayFactor { static constexpr Scalar value = 0.75; };
|
||||
|
||||
|
||||
template<class Scalar>
|
||||
struct NupcolGroupRateTolerance { static constexpr Scalar value = 0.001; };
|
||||
|
||||
@@ -327,9 +329,10 @@ public:
|
||||
|
||||
int nldd_num_initial_newton_iter_{1};
|
||||
int num_local_domains_{0};
|
||||
Scalar local_domain_partition_imbalance_{1.03};
|
||||
std::string local_domain_partition_method_;
|
||||
DomainOrderingMeasure local_domain_ordering_{DomainOrderingMeasure::MaxPressure};
|
||||
Scalar local_domains_partition_imbalance_{1.03};
|
||||
std::string local_domains_partition_method_;
|
||||
int local_domains_partition_well_neighbor_levels_{1};
|
||||
DomainOrderingMeasure local_domains_ordering_{DomainOrderingMeasure::MaxPressure};
|
||||
|
||||
bool write_partitions_{false};
|
||||
|
||||
|
||||
@@ -109,11 +109,24 @@ public:
|
||||
///
|
||||
/// \param[in] zoltan_ctrl Control parameters for on-rank subdomain
|
||||
/// partitioning.
|
||||
///
|
||||
/// \param[in] num_neighbor_levels Number of neighbor levels to include when connecting well cells.
|
||||
/// Default is 0, which means only direct well connections are considered.
|
||||
template <class GridView, class Element>
|
||||
void buildLocalGraph(const GridView& grid_view,
|
||||
const std::vector<Opm::Well>& wells,
|
||||
const std::unordered_map<std::string, std::set<int>>& possibleFutureConnections,
|
||||
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl);
|
||||
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
|
||||
const int num_neighbor_levels);
|
||||
|
||||
/// Connect neighbors of cells up to a specified level.
|
||||
///
|
||||
/// \param[in,out] cells Initial cells to find neighbors of. Updated to include all neighbors
|
||||
/// up to specified level.
|
||||
///
|
||||
/// \param[in] num_levels Number of neighbor levels to include.
|
||||
void connectNeighbors(std::vector<int>& cells,
|
||||
const int num_levels) const;
|
||||
|
||||
/// Partition rank's interior cells into non-overlapping domains using
|
||||
/// the Zoltan graph partitioning software package.
|
||||
@@ -148,6 +161,9 @@ private:
|
||||
/// Zoltan partitioning backend.
|
||||
Opm::ParallelNLDDPartitioningZoltan partitioner_;
|
||||
|
||||
/// Map of cell indices to their neighbors for fast lookup
|
||||
std::unordered_map<int, std::unordered_set<int>> neighbor_map_;
|
||||
|
||||
/// Form internal connectivity graph between rank's reachable, interior
|
||||
/// cells.
|
||||
///
|
||||
@@ -166,7 +182,8 @@ private:
|
||||
template <class GridView, class Element>
|
||||
std::unordered_map<int, int>
|
||||
connectElements(const GridView& grid_view,
|
||||
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl);
|
||||
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
|
||||
const bool build_neighbor_map);
|
||||
|
||||
/// Adapt internal connectivity graph to account for connections induced
|
||||
/// by well reservoir connections.
|
||||
@@ -182,11 +199,15 @@ private:
|
||||
///
|
||||
/// \param[in] g2l Mapping from globally unique cell IDs to local,
|
||||
/// on-rank active cell IDs. Return value from \c connectElements().
|
||||
///
|
||||
/// \param[in] num_neighbor_levels Number of neighbor levels to include when connecting well cells.
|
||||
/// Default is 0, which means only direct well connections are considered.
|
||||
template <typename Comm>
|
||||
void connectWells(const Comm comm,
|
||||
const std::vector<Opm::Well>& wells,
|
||||
std::unordered_map<std::string, std::set<int>> possibleFutureConnections,
|
||||
const std::unordered_map<int, int>& g2l);
|
||||
const std::unordered_map<int, int>& g2l,
|
||||
const int num_neighbor_levels);
|
||||
};
|
||||
|
||||
// Note: "grid_view.size(0)" is intentional here. It is not an error. The
|
||||
@@ -203,9 +224,43 @@ template <class GridView, class Element>
|
||||
void ZoltanPartitioner::buildLocalGraph(const GridView& grid_view,
|
||||
const std::vector<Opm::Well>& wells,
|
||||
const std::unordered_map<std::string, std::set<int>>& possibleFutureConnections,
|
||||
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl)
|
||||
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
|
||||
const int num_neighbor_levels)
|
||||
{
|
||||
this->connectWells(grid_view.comm(), wells, possibleFutureConnections, this->connectElements(grid_view, zoltan_ctrl));
|
||||
const bool build_neighbor_map = num_neighbor_levels > 0;
|
||||
auto g2l = this->connectElements(grid_view, zoltan_ctrl, build_neighbor_map);
|
||||
this->connectWells(grid_view.comm(), wells, possibleFutureConnections, g2l, num_neighbor_levels);
|
||||
}
|
||||
|
||||
void ZoltanPartitioner::connectNeighbors(std::vector<int>& cells,
|
||||
const int num_levels) const
|
||||
{
|
||||
// Initialize search structures
|
||||
std::unordered_set<int> visited(cells.begin(), cells.end());
|
||||
std::vector<int> frontier;
|
||||
std::vector<int> new_frontier;
|
||||
frontier.swap(cells);
|
||||
|
||||
// Breadth-first search for neighbor cells up to num_levels away
|
||||
for (int level = 0; level < num_levels && !frontier.empty(); ++level) {
|
||||
new_frontier.clear();
|
||||
new_frontier.reserve(frontier.size() * 2);
|
||||
|
||||
// Expand current frontier using precomputed neighbor map
|
||||
for (const int cell : frontier) {
|
||||
for (const int neighbor : neighbor_map_.at(cell)) {
|
||||
if (visited.insert(neighbor).second) { // New discovery
|
||||
new_frontier.push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frontier.swap(new_frontier); // Prepare next level
|
||||
}
|
||||
|
||||
// Sort final result for faster processing in later steps
|
||||
cells.assign(visited.begin(), visited.end());
|
||||
std::sort(cells.begin(), cells.end());
|
||||
}
|
||||
|
||||
template <class GridView, class Element>
|
||||
@@ -233,35 +288,30 @@ ZoltanPartitioner::partition(const int num_
|
||||
num_domains = domainId + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix-up for an extreme case: Interior cells whose neighbours are all
|
||||
// on a different rank--i.e., interior cells which do not have on-rank
|
||||
// neighbours. Make each such cell be a separate domain on this rank.
|
||||
// Don't remove this code unless you've taken remedial action elsewhere
|
||||
// to ensure the situation doesn't happen. For what it's worth, we've
|
||||
// seen this case occur in practice when testing on field cases.
|
||||
for (auto& domainId : domains) {
|
||||
if (domainId < 0) {
|
||||
domainId = num_domains++;
|
||||
}
|
||||
}
|
||||
|
||||
return partition;
|
||||
}
|
||||
|
||||
template <class GridView, class Element>
|
||||
std::unordered_map<int, int>
|
||||
ZoltanPartitioner::connectElements(const GridView& grid_view,
|
||||
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl)
|
||||
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
|
||||
const bool build_neighbor_map)
|
||||
{
|
||||
auto g2l = std::unordered_map<int, int>{};
|
||||
if (build_neighbor_map) {
|
||||
neighbor_map_.clear();
|
||||
}
|
||||
|
||||
for (const auto& element : elements(grid_view, Dune::Partitions::interior)) {
|
||||
{
|
||||
const auto c = zoltan_ctrl.index(element);
|
||||
g2l.insert_or_assign(zoltan_ctrl.local_to_global(c), c);
|
||||
}
|
||||
|
||||
if (build_neighbor_map) {
|
||||
// Initialize neighbor set for this cell
|
||||
neighbor_map_[c];
|
||||
}
|
||||
}
|
||||
for (const auto& is : intersections(grid_view, element)) {
|
||||
if (! is.neighbor()) {
|
||||
continue;
|
||||
@@ -282,6 +332,12 @@ ZoltanPartitioner::connectElements(const GridView&
|
||||
|
||||
this->partitioner_.registerConnection(c1, c2);
|
||||
this->partitioner_.registerConnection(c2, c1);
|
||||
|
||||
if (build_neighbor_map) {
|
||||
// Store neighbors in both directions
|
||||
neighbor_map_[c1].insert(c2);
|
||||
neighbor_map_[c2].insert(c1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +348,8 @@ template <typename Comm>
|
||||
void ZoltanPartitioner::connectWells(const Comm comm,
|
||||
const std::vector<Opm::Well>& wells,
|
||||
std::unordered_map<std::string, std::set<int>> possibleFutureConnections,
|
||||
const std::unordered_map<int, int>& g2l)
|
||||
const std::unordered_map<int, int>& g2l,
|
||||
const int num_neighbor_levels)
|
||||
{
|
||||
auto distributedWells = 0;
|
||||
|
||||
@@ -310,7 +367,6 @@ void ZoltanPartitioner::connectWells(const Comm
|
||||
++otherProc;
|
||||
continue;
|
||||
}
|
||||
|
||||
cellIx.push_back(locPos->second);
|
||||
}
|
||||
const auto possibleFutureConnectionSetIt = possibleFutureConnections.find(well.name());
|
||||
@@ -330,17 +386,23 @@ void ZoltanPartitioner::connectWells(const Comm
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto nc = cellIx.size();
|
||||
for (auto ic1 = 0*nc; ic1 < nc; ++ic1) {
|
||||
for (auto ic2 = ic1 + 1; ic2 < nc; ++ic2) {
|
||||
this->partitioner_.registerConnection(cellIx[ic1], cellIx[ic2]);
|
||||
this->partitioner_.registerConnection(cellIx[ic2], cellIx[ic1]);
|
||||
}
|
||||
// Process neighbors and get deduplicated result
|
||||
if (num_neighbor_levels > 0 && !cellIx.empty()) {
|
||||
connectNeighbors(cellIx, num_neighbor_levels);
|
||||
}
|
||||
|
||||
if (! cellIx.empty()) {
|
||||
// All cells intersected by a single well go in the same domain.
|
||||
this->partitioner_.forceSameDomain(std::move(cellIx));
|
||||
const auto nc = cellIx.size();
|
||||
if (nc == 0) {
|
||||
continue;
|
||||
}
|
||||
for (auto ic1 = 0*nc; ic1 < nc-1; ++ic1) {
|
||||
this->partitioner_.registerConnection(cellIx[ic1], cellIx[ic1+1]);
|
||||
this->partitioner_.registerConnection(cellIx[ic1+1], cellIx[ic1]);
|
||||
}
|
||||
|
||||
// Collect all cells for the well into a vertex group
|
||||
if (!cellIx.empty()) {
|
||||
this->partitioner_.addVertexGroup(cellIx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +418,8 @@ This is not supported in the current NLDD implementation.)",
|
||||
};
|
||||
}
|
||||
|
||||
OPM_END_PARALLEL_TRY_CATCH(std::string { "ZoltanPartitioner::connectWells()" }, comm)
|
||||
OPM_END_PARALLEL_TRY_CATCH(std::string{"ZoltanPartitioner::connectWells(): Distributed well detected. "
|
||||
"This is not supported in the current NLDD implementation"}, comm)
|
||||
}
|
||||
|
||||
template <class GridView, class Element>
|
||||
@@ -365,7 +428,8 @@ partitionCellsZoltan(const int num_d
|
||||
const GridView& grid_view,
|
||||
const std::vector<Opm::Well>& wells,
|
||||
const std::unordered_map<std::string, std::set<int>>& possibleFutureConnections,
|
||||
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl)
|
||||
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
|
||||
const int num_neighbor_levels)
|
||||
{
|
||||
if (num_domains <= 1) { // No partitioning => every cell in domain zero.
|
||||
const auto num_interior_cells =
|
||||
@@ -379,7 +443,8 @@ partitionCellsZoltan(const int num_d
|
||||
}
|
||||
|
||||
auto partitioner = ZoltanPartitioner { grid_view, zoltan_ctrl.local_to_global };
|
||||
partitioner.buildLocalGraph(grid_view, wells, possibleFutureConnections, zoltan_ctrl);
|
||||
|
||||
partitioner.buildLocalGraph(grid_view, wells, possibleFutureConnections, zoltan_ctrl, num_neighbor_levels);
|
||||
|
||||
return partitioner.partition(num_domains, grid_view, zoltan_ctrl);
|
||||
}
|
||||
@@ -600,12 +665,13 @@ Opm::partitionCells(const std::string& method,
|
||||
const GridView& grid_view,
|
||||
[[maybe_unused]] const std::vector<Well>& wells,
|
||||
[[maybe_unused]] const std::unordered_map<std::string, std::set<int>>& possibleFutureConnections,
|
||||
[[maybe_unused]] const ZoltanPartitioningControl<Element>& zoltan_ctrl)
|
||||
[[maybe_unused]] const ZoltanPartitioningControl<Element>& zoltan_ctrl,
|
||||
[[maybe_unused]] const int num_neighbor_levels)
|
||||
{
|
||||
if (method == "zoltan") {
|
||||
#if HAVE_MPI && HAVE_ZOLTAN
|
||||
|
||||
return partitionCellsZoltan(num_local_domains, grid_view, wells, possibleFutureConnections, zoltan_ctrl);
|
||||
return partitionCellsZoltan(num_local_domains, grid_view, wells, possibleFutureConnections, zoltan_ctrl, num_neighbor_levels);
|
||||
|
||||
#else // !HAVE_MPI || !HAVE_ZOLTAN
|
||||
|
||||
@@ -700,7 +766,8 @@ Opm::partitionCellsSimple(const int num_cells, const int num_domains)
|
||||
const std::unordered_map<std::string, std::set<int>>&, \
|
||||
const Opm::ZoltanPartitioningControl< \
|
||||
typename std::remove_cv_t<std::remove_reference_t< \
|
||||
decltype(std::declval<Grid>().leafGridView())>>::template Codim<0>::Entity>&)
|
||||
decltype(std::declval<Grid>().leafGridView())>>::template Codim<0>::Entity>&, \
|
||||
const int)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Grid types built into Flow.
|
||||
|
||||
@@ -84,6 +84,8 @@ struct ZoltanPartitioningControl
|
||||
/// \param[in] zoltan_ctrl Control parameters for local Zoltan-based
|
||||
/// partitioning. Not used unless \code method == "zoltan" \endcode.
|
||||
///
|
||||
/// \param[in] num_neighbor_levels Number of neighbor levels to consider for partitioning.
|
||||
///
|
||||
/// \return Partition vector--subdomain ID for each cell in \p grid_view
|
||||
/// traversal order for its interior cells--and the number of subdomains
|
||||
/// on current rank.
|
||||
@@ -94,7 +96,8 @@ partitionCells(const std::string& method,
|
||||
const GridView& grid_view,
|
||||
const std::vector<Well>& wells,
|
||||
const std::unordered_map<std::string, std::set<int>>& possibleFutureConnections,
|
||||
const ZoltanPartitioningControl<Element>& zoltan_ctrl);
|
||||
const ZoltanPartitioningControl<Element>& zoltan_ctrl,
|
||||
const int num_neighbor_levels = 0);
|
||||
|
||||
/// Read a partitioning from file, assumed to contain one number per cell, its partition number.
|
||||
/// \return pair containing a partition vector (partition number for each cell), and the number of partitions.
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -155,6 +157,8 @@ namespace {
|
||||
///
|
||||
/// \param[in] vertexId Enumeration of reachable vertices.
|
||||
///
|
||||
/// \param[in] vertices_to_merge Map of vertices to be merged.
|
||||
///
|
||||
/// \param[in] globalCell Callback for mapping (local) vertex IDs to
|
||||
/// globally unique vertex IDs.
|
||||
template <typename Edge, typename GlobalCellID>
|
||||
@@ -162,6 +166,7 @@ namespace {
|
||||
const std::size_t numVertices,
|
||||
const std::vector<Edge>& edges,
|
||||
const EnumerateSeenVertices& vertexId,
|
||||
const std::vector<std::vector<int>>& vertexGroups,
|
||||
GlobalCellID&& globalCell)
|
||||
: myRank_ { myRank }
|
||||
{
|
||||
@@ -171,7 +176,14 @@ namespace {
|
||||
this->graph_.addConnection(vertexId[v2], vertexId[v1]);
|
||||
}
|
||||
|
||||
this->graph_.compress(vertexId.numVertices());
|
||||
// Apply any vertex merges that were collected
|
||||
for (const auto& group : vertexGroups) {
|
||||
this->graph_.addVertexGroup(group);
|
||||
}
|
||||
|
||||
auto numVerticesAfterMerge = this->graph_.applyVertexMerges();
|
||||
|
||||
this->graph_.compress(numVerticesAfterMerge);
|
||||
|
||||
// Form local-to-global vertex ID mapping for reachable vertices.
|
||||
this->globalCell_.resize(vertexId.numVertices());
|
||||
@@ -220,6 +232,15 @@ namespace {
|
||||
return this->graph_.columnIndices();
|
||||
}
|
||||
|
||||
/// Get the final vertex ID after merging and renumbering for a given original vertex ID.
|
||||
///
|
||||
/// \param[in] originalVertexID The original vertex ID to look up
|
||||
/// \return The final vertex ID after merges and renumbering
|
||||
int getFinalVertexID(const int originalVertexID) const
|
||||
{
|
||||
return this->graph_.getFinalVertexID(originalVertexID);
|
||||
}
|
||||
|
||||
private:
|
||||
// VertexID = int, TrackCompressedIdx = false
|
||||
using Backend = Opm::utility::CSRGraphFromCoordinates<>;
|
||||
@@ -536,27 +557,36 @@ extern "C" {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
void forceSameDomain(const std::vector<int>& cells,
|
||||
std::vector<int>& parts)
|
||||
{
|
||||
if (cells.empty()) { return; }
|
||||
|
||||
const auto first = parts[cells.front()];
|
||||
for (const auto& cell : cells) {
|
||||
parts[cell] = first;
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
void Opm::ParallelNLDDPartitioningZoltan::addVertexGroup(const std::vector<int>& vertices)
|
||||
{
|
||||
vertexGroups_.push_back(vertices);
|
||||
}
|
||||
|
||||
std::vector<int>
|
||||
Opm::ParallelNLDDPartitioningZoltan::partitionElements(const ZoltanParamMap& params) const
|
||||
{
|
||||
const auto vertexId = EnumerateSeenVertices { this->numElements_, this->conns_ };
|
||||
|
||||
// The vertexGraph is built using the reachable indices, so for the vertexGroups_
|
||||
// we need to map the original local indices to the reachable indices
|
||||
std::vector<std::vector<int>> reachableVertexGroups;
|
||||
for (const auto& group : vertexGroups_) {
|
||||
std::vector<int> reachableGroup;
|
||||
for (const int originalLocalIdx : group) {
|
||||
if (const int reachableIdx = vertexId[originalLocalIdx]; reachableIdx != -1) {
|
||||
reachableGroup.push_back(reachableIdx);
|
||||
}
|
||||
}
|
||||
if (!reachableGroup.empty()) {
|
||||
reachableVertexGroups.push_back(reachableGroup);
|
||||
}
|
||||
}
|
||||
|
||||
auto graph = VertexGraph {
|
||||
this->comm_.rank(), this->numElements_,
|
||||
this->conns_, vertexId, this->globalCell_
|
||||
this->conns_, vertexId, reachableVertexGroups, this->globalCell_
|
||||
};
|
||||
|
||||
const auto partsForReachableCells = Partitioner {
|
||||
@@ -567,13 +597,11 @@ Opm::ParallelNLDDPartitioningZoltan::partitionElements(const ZoltanParamMap& par
|
||||
auto parts = std::vector<int>(this->numElements_, -1);
|
||||
for (auto elm = 0*this->numElements_; elm < this->numElements_; ++elm) {
|
||||
if (const auto reachableElmIx = vertexId[elm]; reachableElmIx >= 0) {
|
||||
parts[elm] = partsForReachableCells[reachableElmIx];
|
||||
// Get the final vertex ID after merging and renumbering
|
||||
const auto finalVertexId = graph.getFinalVertexID(reachableElmIx);
|
||||
parts[elm] = partsForReachableCells[finalVertexId];
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& cells : this->sameDomain_) {
|
||||
::forceSameDomain(cells, parts);
|
||||
}
|
||||
|
||||
return util::compressPartitionIDs(std::move(parts));
|
||||
}
|
||||
@@ -72,18 +72,6 @@ namespace Opm {
|
||||
this->conns_.emplace_back(c1, c2);
|
||||
}
|
||||
|
||||
/// Force collection of cells to be in same result domain.
|
||||
///
|
||||
/// Mostly as a means to ensuring wells do not intersect multiple
|
||||
/// domains/blocks.
|
||||
///
|
||||
/// \param[in] cells Cell collection. Typically those cells which
|
||||
/// are intersected by a single well.
|
||||
void forceSameDomain(std::vector<int>&& cells)
|
||||
{
|
||||
this->sameDomain_.emplace_back(std::move(cells));
|
||||
}
|
||||
|
||||
/// Partition connectivity graph using Zoltan graph partitioning
|
||||
/// package.
|
||||
///
|
||||
@@ -99,9 +87,16 @@ namespace Opm {
|
||||
std::vector<int>
|
||||
partitionElements(const ZoltanParamMap& params) const;
|
||||
|
||||
/// Add a group of vertices that should be merged together.
|
||||
/// Must be called before compress().
|
||||
///
|
||||
/// \param[in] vertices Vector of vertex IDs to merge
|
||||
void addVertexGroup(const std::vector<int>& vertices);
|
||||
|
||||
private:
|
||||
/// Connection/graph edge.
|
||||
using Connection = std::pair<std::size_t, std::size_t>;
|
||||
std::vector<std::vector<int>> vertexGroups_{};
|
||||
|
||||
/// MPI communication object. Needed by Zoltan.
|
||||
Parallel::Communication comm_{};
|
||||
@@ -116,12 +111,6 @@ namespace Opm {
|
||||
|
||||
/// Connectivity graph edges.
|
||||
std::vector<Connection> conns_{};
|
||||
|
||||
/// Collections of vertices/cells which must be coalesced to the
|
||||
/// same domain/block. All vertices within a single collection will
|
||||
/// be placed on the same domain, but cells from different
|
||||
/// collections may be placed on different domains.
|
||||
std::vector<std::vector<int>> sameDomain_{};
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2021 SINTEF Digital, Mathematics and Cybernetics.
|
||||
Copyright 2021,2025 SINTEF Digital, Mathematics and Cybernetics.
|
||||
Copyright 2025 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
@@ -19,11 +20,84 @@
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#define BOOST_TEST_MODULE OPM_test_aspinPartition
|
||||
#define BOOST_TEST_MODULE OPM_test_partitionCells
|
||||
#define BOOST_TEST_NO_MAIN
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <dune/grid/common/gridview.hh>
|
||||
|
||||
#include <opm/grid/common/WellConnections.hpp>
|
||||
#include <opm/grid/CpGrid.hpp>
|
||||
#include <opm/grid/utility/OpmWellType.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/Well/Connection.hpp>
|
||||
#include <opm/input/eclipse/Schedule/Well/Well.hpp>
|
||||
#include <opm/input/eclipse/Schedule/Well/WellConnections.hpp>
|
||||
#include <opm/input/eclipse/Parser/Parser.hpp>
|
||||
#include <opm/input/eclipse/Deck/Deck.hpp>
|
||||
#include <opm/input/eclipse/EclipseState/Grid/EclipseGrid.hpp>
|
||||
|
||||
#include <opm/simulators/flow/partitionCells.hpp>
|
||||
|
||||
// Helper functions
|
||||
namespace {
|
||||
Opm::Connection createConnection(int i, int j, int k, int global_index) {
|
||||
return Opm::Connection(i, j, k, // i,j,k
|
||||
global_index, // global_index
|
||||
0, // complnum
|
||||
Opm::Connection::State::OPEN, // state
|
||||
Opm::Connection::Direction::Z, // direction
|
||||
Opm::Connection::CTFKind::DeckValue, // kind
|
||||
0, // satTableId
|
||||
0.0, // depth
|
||||
Opm::Connection::CTFProperties(), // properties
|
||||
0, // sort_value
|
||||
false); // defaultSatTabId
|
||||
}
|
||||
|
||||
Dune::cpgrid::OpmWellType createWell(const std::string& name) {
|
||||
using namespace Opm;
|
||||
return Dune::cpgrid::OpmWellType(name, name, 0, 0, 0, 0, 0.0, WellType(),
|
||||
Well::ProducerCMode(), Connection::Order::TRACK,
|
||||
UnitSystem::newMETRIC(),
|
||||
0.0, 0.0, false, false, 0, Well::GasInflowEquation());
|
||||
}
|
||||
|
||||
std::vector<Dune::cpgrid::OpmWellType> createWellsWithConnections(
|
||||
const std::vector<std::pair<std::string, std::vector<int>>>& well_specs) {
|
||||
std::vector<Dune::cpgrid::OpmWellType> wells;
|
||||
for (const auto& [well_name, cell_indices] : well_specs) {
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
for (int idx : cell_indices) {
|
||||
well_conn->add(createConnection(idx, 0, 0, idx));
|
||||
}
|
||||
auto well = createWell(well_name);
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
return wells;
|
||||
}
|
||||
|
||||
Dune::CpGrid createTestGrid(const std::array<int, 3>& dims,
|
||||
const std::array<double, 3>& size) {
|
||||
Dune::CpGrid grid;
|
||||
grid.createCartesian(dims, size);
|
||||
return grid;
|
||||
}
|
||||
|
||||
template<typename Entity>
|
||||
Opm::ZoltanPartitioningControl<Entity> createZoltanControl(const Dune::CpGrid& grid) {
|
||||
Opm::ZoltanPartitioningControl<Entity> zoltan_ctrl;
|
||||
zoltan_ctrl.domain_imbalance = 1.1;
|
||||
zoltan_ctrl.index = [](const auto& element) {
|
||||
return element.index();
|
||||
};
|
||||
zoltan_ctrl.local_to_global = [&grid](const int local_idx) {
|
||||
return grid.globalCell()[local_idx];
|
||||
};
|
||||
return zoltan_ctrl;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(FileBased)
|
||||
{
|
||||
@@ -54,6 +128,550 @@ BOOST_AUTO_TEST_CASE(Simple2)
|
||||
BOOST_CHECK_EQUAL(num_part, 7);
|
||||
std::vector<int> expected = { 0, 0, 1, 1, 2, 2, 3, 4, 5, 6 };
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), part.begin(), part.end());
|
||||
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(PartitionCellsTest)
|
||||
{
|
||||
auto grid = createTestGrid({3, 4, 1}, {3.0, 4.0, 1.0});
|
||||
using Entity = typename Dune::CpGrid::LeafGridView::template Codim<0>::Entity;
|
||||
auto zoltan_ctrl = createZoltanControl<Entity>(grid);
|
||||
|
||||
// Test simple partitioning
|
||||
{
|
||||
auto [part, num_part] = Opm::partitionCells("simple", 3, grid.leafGridView(),
|
||||
std::vector<Opm::Well>{},
|
||||
std::unordered_map<std::string, std::set<int>>{},
|
||||
zoltan_ctrl);
|
||||
BOOST_CHECK_EQUAL(num_part, 3);
|
||||
BOOST_CHECK_EQUAL(part.size(), 12);
|
||||
|
||||
// Check that all partition numbers are valid
|
||||
for (const auto& p : part) {
|
||||
BOOST_CHECK(p >= 0 && p < 3);
|
||||
}
|
||||
}
|
||||
|
||||
#if HAVE_MPI && HAVE_ZOLTAN
|
||||
// Test Zoltan partitioning
|
||||
{
|
||||
auto [part, num_part] = Opm::partitionCells("zoltan", 3, grid.leafGridView(),
|
||||
std::vector<Opm::Well>{},
|
||||
std::unordered_map<std::string, std::set<int>>{},
|
||||
zoltan_ctrl);
|
||||
BOOST_CHECK_EQUAL(num_part, 3);
|
||||
BOOST_CHECK_EQUAL(part.size(), 12);
|
||||
|
||||
// Check that all partition numbers are valid
|
||||
for (const auto& p : part) {
|
||||
BOOST_CHECK(p >= 0 && p < 3);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(PartitionCellsWithWellMergeTest)
|
||||
{
|
||||
// Create a 1D grid with 7 cells
|
||||
// Visual representation of the grid and wells:
|
||||
// Cell indices: 0 1 2 3 4 5 6
|
||||
// Well layout: W1---W1 | W2---W2 | |
|
||||
// Expected: Cell 0 and 1 should be merged
|
||||
// Cell 3 and 4 should be merged
|
||||
// Cells 2, 5, and 6 can be in any partition
|
||||
auto grid = createTestGrid({7, 1, 1}, {7.0, 1.0, 1.0});
|
||||
|
||||
// Setup Zoltan control parameters
|
||||
using Entity = typename Dune::CpGrid::LeafGridView::template Codim<0>::Entity;
|
||||
auto zoltan_ctrl = createZoltanControl<Entity>(grid);
|
||||
|
||||
auto wells = createWellsWithConnections({
|
||||
{"TESTW1", {0, 1}}, // Well 1 connects cells 0,1
|
||||
{"TESTW2", {3, 4}} // Well 2 connects cells 3,4
|
||||
});
|
||||
|
||||
// Create well connections object
|
||||
Dune::cpgrid::WellConnections wellConnections(wells, std::unordered_map<std::string, std::set<int>>{}, grid);
|
||||
|
||||
#if HAVE_MPI && HAVE_ZOLTAN
|
||||
// Test Zoltan partitioning with well cells
|
||||
auto [part, num_part] = Opm::partitionCells("zoltan", 4, grid.leafGridView(),
|
||||
wells,
|
||||
std::unordered_map<std::string, std::set<int>>{},
|
||||
zoltan_ctrl);
|
||||
|
||||
// Verify number of partitions
|
||||
BOOST_CHECK_EQUAL(num_part, 4);
|
||||
BOOST_CHECK_EQUAL(part.size(), 7);
|
||||
|
||||
// The key tests:
|
||||
// 1. Cells 0 and 1 (well1) should be in the same partition
|
||||
BOOST_CHECK_EQUAL(part[0], part[1]);
|
||||
|
||||
// 2. Cells 3 and 4 (well2) should be in the same partition
|
||||
BOOST_CHECK_EQUAL(part[3], part[4]);
|
||||
|
||||
// 3. Cells 2, 5, and 6 can be in any partition, but well1 and well2 cells
|
||||
// don't need to be in the same partition
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(PartitionCellsWithOverlappingWellsTest)
|
||||
{
|
||||
// Create a 1D grid with 7 cells
|
||||
// Visual representation of the grid and wells:
|
||||
// Cell indices: 0 1 2 3 4 5 6
|
||||
// Well layout: W1---W1---W1 | | |
|
||||
// W2---W2
|
||||
// Expected: Cells 1,2,3 should be in same partition (Well 1)
|
||||
// Cells 3,4 should be in same partition (Well 2)
|
||||
// Therefore cells 1,2,3,4 should all end up in same partition
|
||||
|
||||
auto grid = createTestGrid({7, 1, 1}, {7.0, 1.0, 1.0});
|
||||
using Entity = typename Dune::CpGrid::LeafGridView::template Codim<0>::Entity;
|
||||
auto zoltan_ctrl = createZoltanControl<Entity>(grid);
|
||||
|
||||
auto wells = createWellsWithConnections({
|
||||
{"TESTW1", {1, 2, 3}}, // Well 1 connects cells 1,2,3
|
||||
{"TESTW2", {3, 4}} // Well 2 connects cells 3,4
|
||||
});
|
||||
|
||||
#if HAVE_MPI && HAVE_ZOLTAN
|
||||
// Test Zoltan partitioning with overlapping well cells
|
||||
auto [part, num_part] = Opm::partitionCells("zoltan", 4, grid.leafGridView(),
|
||||
wells,
|
||||
std::unordered_map<std::string, std::set<int>>{},
|
||||
zoltan_ctrl);
|
||||
|
||||
// Verify number of partitions
|
||||
BOOST_CHECK_EQUAL(num_part, 4);
|
||||
BOOST_CHECK_EQUAL(part.size(), 7);
|
||||
|
||||
// The key tests:
|
||||
// 1. All cells connected by Well 1 should be in the same partition
|
||||
BOOST_CHECK_EQUAL(part[1], part[2]);
|
||||
BOOST_CHECK_EQUAL(part[2], part[3]);
|
||||
|
||||
// 2. All cells connected by Well 2 should be in the same partition
|
||||
BOOST_CHECK_EQUAL(part[3], part[4]);
|
||||
|
||||
// 3. Due to the overlap at cell 3, all well-connected cells should be in the same partition
|
||||
BOOST_CHECK_EQUAL(part[1], part[4]);
|
||||
|
||||
// 4. Cells 0, 5, and 6 can be in any partition
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(PartitionCellsWithOverlappingWells3DTest)
|
||||
{
|
||||
// Create a 3x3x3 grid with three wells:
|
||||
// - Well1: Vertical well through (1,1,0) -> (1,1,1) -> (1,1,2)
|
||||
// - Well2: Diagonal well through (0,0,0) -> (1,1,1) -> (2,2,2) [overlaps with Well1 at (1,1,1)]
|
||||
// - Well3: Horizontal well through (0,2,0) -> (1,2,0) -> (2,2,0) [no overlap]
|
||||
|
||||
auto grid = createTestGrid({3, 3, 3}, {3.0, 3.0, 3.0});
|
||||
using Entity = typename Dune::CpGrid::LeafGridView::template Codim<0>::Entity;
|
||||
auto zoltan_ctrl = createZoltanControl<Entity>(grid);
|
||||
|
||||
// Helper to convert i,j,k coordinates to global index for 3x3x3 grid
|
||||
auto ijkToGlobal = [](int i, int j, int k) { return i + (3 * j) + (9 * k); };
|
||||
|
||||
// Create wells with proper 3D connections
|
||||
auto wells = std::vector<Dune::cpgrid::OpmWellType>();
|
||||
|
||||
// Well 1 - vertical well
|
||||
{
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
well_conn->add(createConnection(1, 1, 0, ijkToGlobal(1, 1, 0))); // Global index: 4
|
||||
well_conn->add(createConnection(1, 1, 1, ijkToGlobal(1, 1, 1))); // Global index: 13
|
||||
well_conn->add(createConnection(1, 1, 2, ijkToGlobal(1, 1, 2))); // Global index: 22
|
||||
auto well = createWell("VERTICAL");
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
|
||||
// Well 2 - diagonal well (overlaps with Well 1 at (1,1,1))
|
||||
{
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
well_conn->add(createConnection(0, 0, 0, ijkToGlobal(0, 0, 0))); // Global index: 0
|
||||
well_conn->add(createConnection(1, 1, 1, ijkToGlobal(1, 1, 1))); // Global index: 13
|
||||
well_conn->add(createConnection(2, 2, 2, ijkToGlobal(2, 2, 2))); // Global index: 26
|
||||
auto well = createWell("DIAGONAL");
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
|
||||
// Well 3 - horizontal well (no overlap)
|
||||
{
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
well_conn->add(createConnection(0, 2, 0, ijkToGlobal(0, 2, 0))); // Global index: 6
|
||||
well_conn->add(createConnection(1, 2, 0, ijkToGlobal(1, 2, 0))); // Global index: 7
|
||||
well_conn->add(createConnection(2, 2, 0, ijkToGlobal(2, 2, 0))); // Global index: 8
|
||||
auto well = createWell("HORIZONTAL");
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
|
||||
// Create well connections object
|
||||
Dune::cpgrid::WellConnections wellConnections(wells, std::unordered_map<std::string, std::set<int>>{}, grid);
|
||||
|
||||
#if HAVE_MPI && HAVE_ZOLTAN
|
||||
// Test Zoltan partitioning with overlapping well cells
|
||||
auto [part, num_part] = Opm::partitionCells("zoltan", 10, grid.leafGridView(),
|
||||
wells,
|
||||
std::unordered_map<std::string, std::set<int>>{},
|
||||
zoltan_ctrl);
|
||||
|
||||
// Verify number of partitions
|
||||
BOOST_CHECK_EQUAL(num_part, 10);
|
||||
BOOST_CHECK_EQUAL(part.size(), 27); // 3x3x3 grid
|
||||
|
||||
// The key tests:
|
||||
// 1. All cells in Well 1 (vertical) should be in the same partition
|
||||
BOOST_CHECK_EQUAL(part[ijkToGlobal(1,1,0)], part[ijkToGlobal(1,1,1)]);
|
||||
BOOST_CHECK_EQUAL(part[ijkToGlobal(1,1,1)], part[ijkToGlobal(1,1,2)]);
|
||||
|
||||
// 2. All cells in Well 2 (diagonal) should be in the same partition
|
||||
BOOST_CHECK_EQUAL(part[ijkToGlobal(0,0,0)], part[ijkToGlobal(1,1,1)]);
|
||||
BOOST_CHECK_EQUAL(part[ijkToGlobal(1,1,1)], part[ijkToGlobal(2,2,2)]);
|
||||
|
||||
// 3. Due to overlap at (1,1,1), all cells from Well 1 and Well 2 should be in same partition
|
||||
BOOST_CHECK_EQUAL(part[ijkToGlobal(1,1,0)], part[ijkToGlobal(0,0,0)]);
|
||||
BOOST_CHECK_EQUAL(part[ijkToGlobal(1,1,2)], part[ijkToGlobal(2,2,2)]);
|
||||
|
||||
// 4. All cells in Well 3 (horizontal) should be in the same partition
|
||||
BOOST_CHECK_EQUAL(part[ijkToGlobal(0,2,0)], part[ijkToGlobal(1,2,0)]);
|
||||
BOOST_CHECK_EQUAL(part[ijkToGlobal(1,2,0)], part[ijkToGlobal(2,2,0)]);
|
||||
|
||||
// 5. Well 3 can be in a different partition from Well 1 and 2 since it doesn't overlap
|
||||
// No test needed as it can be in any partition
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(PartitionCellsComplexWellNetworkTest)
|
||||
{
|
||||
// Create a 5x5x4 grid to accommodate a complex well network
|
||||
auto grid = createTestGrid({5, 5, 4}, {5.0, 5.0, 4.0});
|
||||
using Entity = typename Dune::CpGrid::LeafGridView::template Codim<0>::Entity;
|
||||
auto zoltan_ctrl = createZoltanControl<Entity>(grid);
|
||||
|
||||
// Helper to convert i,j,k coordinates to global index for 5x5x4 grid
|
||||
auto ijkToGlobal = [](int i, int j, int k) { return i + (5 * j) + (25 * k); };
|
||||
|
||||
// Create wells with complex connections
|
||||
auto wells = std::vector<Dune::cpgrid::OpmWellType>();
|
||||
|
||||
// Well 1 - Main vertical producer with two branches
|
||||
{
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
// Main wellbore - vertical
|
||||
well_conn->add(createConnection(2, 2, 0, ijkToGlobal(2, 2, 0))); // Bottom
|
||||
well_conn->add(createConnection(2, 2, 1, ijkToGlobal(2, 2, 1)));
|
||||
well_conn->add(createConnection(2, 2, 2, ijkToGlobal(2, 2, 2)));
|
||||
well_conn->add(createConnection(2, 2, 3, ijkToGlobal(2, 2, 3))); // Top
|
||||
// Branch 1 - horizontal in x direction
|
||||
well_conn->add(createConnection(3, 2, 2, ijkToGlobal(3, 2, 2)));
|
||||
well_conn->add(createConnection(4, 2, 2, ijkToGlobal(4, 2, 2)));
|
||||
// Branch 2 - horizontal in y direction
|
||||
well_conn->add(createConnection(2, 3, 1, ijkToGlobal(2, 3, 1)));
|
||||
well_conn->add(createConnection(2, 4, 1, ijkToGlobal(2, 4, 1)));
|
||||
auto well = createWell("PRODUCER1");
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
|
||||
// Well 2 - Diagonal injector crossing the producer
|
||||
{
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
well_conn->add(createConnection(0, 0, 0, ijkToGlobal(0, 0, 0)));
|
||||
well_conn->add(createConnection(1, 1, 1, ijkToGlobal(1, 1, 1)));
|
||||
well_conn->add(createConnection(2, 2, 2, ijkToGlobal(2, 2, 2))); // Intersects with Well 1
|
||||
well_conn->add(createConnection(3, 3, 3, ijkToGlobal(3, 3, 3)));
|
||||
auto well = createWell("INJECTOR1");
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
|
||||
// Well 3 - Horizontal well that crosses both previous wells
|
||||
{
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
well_conn->add(createConnection(0, 2, 2, ijkToGlobal(0, 2, 2)));
|
||||
well_conn->add(createConnection(1, 2, 2, ijkToGlobal(1, 2, 2)));
|
||||
well_conn->add(createConnection(2, 2, 2, ijkToGlobal(2, 2, 2))); // Intersects with Well 1 and 2
|
||||
well_conn->add(createConnection(3, 2, 2, ijkToGlobal(3, 2, 2))); // Intersects with Well 1's branch
|
||||
well_conn->add(createConnection(4, 2, 2, ijkToGlobal(4, 2, 2)));
|
||||
auto well = createWell("PRODUCER2");
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
|
||||
// Well 4 - L-shaped well connecting to the network
|
||||
{
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
well_conn->add(createConnection(2, 4, 1, ijkToGlobal(2, 4, 1))); // Connects to Well 1's branch
|
||||
well_conn->add(createConnection(2, 4, 2, ijkToGlobal(2, 4, 2)));
|
||||
well_conn->add(createConnection(2, 4, 3, ijkToGlobal(2, 4, 3)));
|
||||
well_conn->add(createConnection(3, 4, 3, ijkToGlobal(3, 4, 3)));
|
||||
well_conn->add(createConnection(4, 4, 3, ijkToGlobal(4, 4, 3)));
|
||||
auto well = createWell("PRODUCER3");
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
|
||||
// Well 5 - Isolated well (not connected to the network)
|
||||
{
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
well_conn->add(createConnection(0, 4, 0, ijkToGlobal(0, 4, 0)));
|
||||
well_conn->add(createConnection(1, 4, 0, ijkToGlobal(1, 4, 0)));
|
||||
auto well = createWell("ISOLATED");
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
|
||||
#if HAVE_MPI && HAVE_ZOLTAN
|
||||
// Test Zoltan partitioning with complex well network
|
||||
auto [part, num_part] = Opm::partitionCells("zoltan", 15, grid.leafGridView(),
|
||||
wells,
|
||||
std::unordered_map<std::string, std::set<int>>{},
|
||||
zoltan_ctrl);
|
||||
|
||||
// Verify number of partitions and grid size
|
||||
BOOST_CHECK_EQUAL(num_part, 15);
|
||||
BOOST_CHECK_EQUAL(part.size(), 100); // 5x5x4 grid
|
||||
|
||||
// Helper to check if two cells are in the same partition
|
||||
auto inSamePartition = [&part](int idx1, int idx2) {
|
||||
return part[idx1] == part[idx2];
|
||||
};
|
||||
|
||||
// Test 1: All cells in Well 1 (main bore and branches) should be in the same partition
|
||||
// Main wellbore
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(2,2,0), ijkToGlobal(2,2,1)));
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(2,2,1), ijkToGlobal(2,2,2)));
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(2,2,2), ijkToGlobal(2,2,3)));
|
||||
// Branch 1
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(2,2,2), ijkToGlobal(3,2,2)));
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(3,2,2), ijkToGlobal(4,2,2)));
|
||||
// Branch 2
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(2,2,1), ijkToGlobal(2,3,1)));
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(2,3,1), ijkToGlobal(2,4,1)));
|
||||
|
||||
// Test 2: Well 2 (diagonal) should be in same partition as Well 1 due to intersection
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(0,0,0), ijkToGlobal(2,2,2)));
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(3,3,3), ijkToGlobal(2,2,2)));
|
||||
|
||||
// Test 3: Well 3 (horizontal) should be in same partition as Well 1 and 2
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(0,2,2), ijkToGlobal(2,2,2)));
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(4,2,2), ijkToGlobal(2,2,2)));
|
||||
|
||||
// Test 4: Well 4 (L-shaped) should be in same partition as Well 1 due to connection
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(2,4,1), ijkToGlobal(2,2,1)));
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(4,4,3), ijkToGlobal(2,2,1)));
|
||||
|
||||
// Test 5: Well 5 (isolated) should maintain its own cells in same partition
|
||||
BOOST_CHECK(inSamePartition(ijkToGlobal(0,4,0), ijkToGlobal(1,4,0)));
|
||||
// But should be in different partition from the connected network
|
||||
BOOST_CHECK(part[ijkToGlobal(0,4,0)] != part[ijkToGlobal(2,2,2)]);
|
||||
|
||||
// Test 6: Verify the connected network (Wells 1-4) forms a single partition
|
||||
std::set<int> network_partition_ids;
|
||||
network_partition_ids.insert(part[ijkToGlobal(2,2,2)]); // Well 1
|
||||
network_partition_ids.insert(part[ijkToGlobal(1,1,1)]); // Well 2
|
||||
network_partition_ids.insert(part[ijkToGlobal(2,2,2)]); // Well 3
|
||||
network_partition_ids.insert(part[ijkToGlobal(2,4,1)]); // Well 4
|
||||
BOOST_CHECK_EQUAL(network_partition_ids.size(), 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(PartitionCellsWithNonReachableCellsTest)
|
||||
{
|
||||
// Create a 3x3x3 grid where:
|
||||
// - cell (0,0,0) is isolated (surrounded by inactive cells)
|
||||
// - cell (0,2,0) is isolated (surrounded by inactive cells)
|
||||
// - cells needed for wells are active
|
||||
// Grid structure (top view):
|
||||
// Layer k=0: Layer k=1: Layer k=2: W = well cells
|
||||
// [I][X][X ] [X][ ][ ] [X][ ][ ] X = inactive cells
|
||||
// [X][X][W2] [X][W1][W2] [X][W1][W2] I = isolated active cell
|
||||
// [I][X][ ] [X][ ][ ] [X][ ][ ] [ ] = active cells
|
||||
const std::string deckString =
|
||||
R"(RUNSPEC
|
||||
|
||||
DIMENS
|
||||
3 3 3 /
|
||||
|
||||
GRID
|
||||
COORD
|
||||
0 0 0 0 0 1
|
||||
1 0 0 1 0 1
|
||||
2 0 0 2 0 1
|
||||
3 0 0 3 0 1
|
||||
0 1 0 0 1 1
|
||||
1 1 0 1 1 1
|
||||
2 1 0 2 1 1
|
||||
3 1 0 3 1 1
|
||||
0 2 0 0 2 1
|
||||
1 2 0 1 2 1
|
||||
2 2 0 2 2 1
|
||||
3 2 0 3 2 1
|
||||
0 3 0 0 3 1
|
||||
1 3 0 1 3 1
|
||||
2 3 0 2 3 1
|
||||
3 3 0 3 3 1
|
||||
/
|
||||
|
||||
ZCORN
|
||||
36*0
|
||||
36*1
|
||||
36*1
|
||||
36*2
|
||||
36*2
|
||||
36*3
|
||||
/
|
||||
|
||||
ACTNUM
|
||||
-- First layer (3x3)
|
||||
1 0 0 -- cell (0,0,0) is isolated
|
||||
0 0 1
|
||||
1 0 1 -- cell (1,1,1) is isolated
|
||||
-- Middle layer (3x3)
|
||||
0 1 1
|
||||
0 1 1
|
||||
0 1 1
|
||||
-- Top layer (3x3)
|
||||
0 1 1
|
||||
0 1 1
|
||||
0 1 1
|
||||
/
|
||||
|
||||
END
|
||||
)";
|
||||
|
||||
// Parse and create grid
|
||||
Opm::Parser parser;
|
||||
const auto deck = parser.parseString(deckString);
|
||||
Opm::EclipseGrid ecl_grid(deck);
|
||||
Dune::CpGrid grid;
|
||||
grid.processEclipseFormat(&ecl_grid, nullptr, false, false, false);
|
||||
|
||||
// Verify grid properties
|
||||
const auto& grid_view = grid.leafGridView();
|
||||
const std::size_t num_active_cells = grid_view.size(0);
|
||||
BOOST_CHECK_EQUAL(num_active_cells, 16); // 27 total cells - 11 inactive = 16 active
|
||||
|
||||
// Helper functions
|
||||
using Entity = typename Dune::CpGrid::LeafGridView::template Codim<0>::Entity;
|
||||
auto zoltan_ctrl = createZoltanControl<Entity>(grid);
|
||||
auto ijkToGlobal = [](int i, int j, int k) { return i + (3 * j) + (9 * k); };
|
||||
|
||||
auto wells = std::vector<Dune::cpgrid::OpmWellType>();
|
||||
// Well 1 - Middle column well
|
||||
{
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
well_conn->add(createConnection(1, 1, 1, ijkToGlobal(1, 1, 1)));
|
||||
well_conn->add(createConnection(1, 1, 2, ijkToGlobal(1, 1, 2)));
|
||||
auto well = createWell("WELL1");
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
|
||||
// Well 2 - Right column well
|
||||
{
|
||||
auto well_conn = std::make_shared<Opm::WellConnections>();
|
||||
well_conn->add(createConnection(2, 1, 0, ijkToGlobal(2, 1, 0)));
|
||||
well_conn->add(createConnection(2, 1, 1, ijkToGlobal(2, 1, 1)));
|
||||
well_conn->add(createConnection(2, 1, 2, ijkToGlobal(2, 1, 2)));
|
||||
auto well = createWell("WELL2");
|
||||
well.updateConnections(well_conn, true);
|
||||
wells.push_back(well);
|
||||
}
|
||||
|
||||
#if HAVE_MPI && HAVE_ZOLTAN
|
||||
auto [part, num_part] = Opm::partitionCells("zoltan", 5, grid.leafGridView(),
|
||||
wells,
|
||||
std::unordered_map<std::string, std::set<int>>{},
|
||||
zoltan_ctrl);
|
||||
|
||||
BOOST_CHECK_EQUAL(num_part, 5);
|
||||
BOOST_CHECK_EQUAL(part.size(), 16);
|
||||
|
||||
// For this test global indices != local indices, so we need to
|
||||
// create a map from global index to local index
|
||||
std::map<int, int> g2l;
|
||||
for (const auto& element : elements(grid.leafGridView(), Dune::Partitions::interior)) {
|
||||
const auto globalIndex = grid.globalCell()[zoltan_ctrl.index(element)];
|
||||
g2l[globalIndex] = zoltan_ctrl.index(element);
|
||||
}
|
||||
|
||||
// Verify that the two isolated cells are assigned to the -1 partition
|
||||
BOOST_CHECK_EQUAL(part[g2l[ijkToGlobal(0,0,0)]], -1);
|
||||
BOOST_CHECK_EQUAL(part[g2l[ijkToGlobal(0,2,0)]], -1);
|
||||
|
||||
// Well 1: All cells should be in the same partition
|
||||
BOOST_CHECK(part[g2l[ijkToGlobal(1,1,1)]] == part[g2l[ijkToGlobal(1,1,2)]]);
|
||||
|
||||
// Well 2: All cells should be in the same partition
|
||||
BOOST_CHECK(part[g2l[ijkToGlobal(2,1,0)]] == part[g2l[ijkToGlobal(2,1,1)]]);
|
||||
BOOST_CHECK(part[g2l[ijkToGlobal(2,1,1)]] == part[g2l[ijkToGlobal(2,1,2)]]);
|
||||
|
||||
// Wells can be in different partitions as they don't connect
|
||||
BOOST_CHECK(part[g2l[ijkToGlobal(1,1,1)]] != part[g2l[ijkToGlobal(2,1,2)]]);
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(PartitionCellsWithNeighborConnectivityTest)
|
||||
{
|
||||
// Create a 1D grid with 7 cells and two wells that are not directly connected
|
||||
// but will be connected through their neighboring cells when using neighbor_levels=1
|
||||
// Visual representation:
|
||||
// Cell indices: 0 1 2 3 4 5 6
|
||||
// Well layout: W1 W2
|
||||
// Connected: * * * * *
|
||||
// The wells will be connected because cell 2 is a neighbor of W1 and W2
|
||||
auto grid = createTestGrid({7, 1, 1}, {7.0, 1.0, 1.0});
|
||||
using Entity = typename Dune::CpGrid::LeafGridView::template Codim<0>::Entity;
|
||||
auto zoltan_ctrl = createZoltanControl<Entity>(grid);
|
||||
|
||||
// Create two wells that are not directly connected
|
||||
auto wells = createWellsWithConnections({
|
||||
{"WELL1", {1}}, // Well 1 in cell 1
|
||||
{"WELL2", {3}} // Well 2 in cell 3 (not adjacent to Well 1)
|
||||
});
|
||||
|
||||
#if HAVE_MPI && HAVE_ZOLTAN
|
||||
// Test Zoltan partitioning with neighbor connectivity
|
||||
auto [part, num_part] = Opm::partitionCells("zoltan", 3, grid.leafGridView(),
|
||||
wells,
|
||||
std::unordered_map<std::string, std::set<int>>{},
|
||||
zoltan_ctrl,
|
||||
1);
|
||||
|
||||
// Verify number of partitions
|
||||
BOOST_CHECK_EQUAL(num_part, 3);
|
||||
BOOST_CHECK_EQUAL(part.size(), 7); // 7x1x1 grid
|
||||
|
||||
// Check that the wells are connected through their neighbors
|
||||
BOOST_CHECK(part[0] == part[1]);
|
||||
BOOST_CHECK(part[1] == part[2]);
|
||||
BOOST_CHECK(part[2] == part[3]);
|
||||
BOOST_CHECK(part[3] == part[4]);
|
||||
|
||||
// Check that the remaining cells are in different partitions
|
||||
BOOST_CHECK(part[0] != part[5]);
|
||||
BOOST_CHECK(part[5] != part[6]);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
init_unit_test_func()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Dune::MPIHelper::instance(argc, argv);
|
||||
boost::unit_test::unit_test_main(&init_unit_test_func,
|
||||
argc, argv);
|
||||
}
|
||||
Reference in new issue
Block a user