///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2023- Equinor ASA // // ResInsight is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RigMswCenterLineCalculator.h" #include "RiaLogging.h" #include "RigCell.h" #include "RigCellFaceGeometryTools.h" #include "RigEclipseCaseData.h" #include "RigMainGrid.h" #include "RigSimWellData.h" #include "RigWellResultFrame.h" #include "RimEclipseCase.h" #include "RimEclipseView.h" #include "RimSimWellInView.h" #include "RimSimWellInViewCollection.h" #include "cvfRay.h" //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RigMswCenterLineCalculator::calculateMswWellPipeGeometry( RimSimWellInView* rimWell ) { CVF_ASSERT( rimWell ); const RigSimWellData* simWellData = rimWell->simWellData(); if ( !simWellData ) return {}; auto eclipseView = rimWell->firstAncestorOrThisOfTypeAsserted(); if ( eclipseView->eclipseCase() && eclipseView->eclipseCase()->eclipseCaseData() ) { auto eclipseCaseData = eclipseView->eclipseCase()->eclipseCaseData(); int timeStepIndex = eclipseView->currentTimeStep(); return calculateMswWellPipeGeometryForTimeStep( eclipseCaseData, simWellData, timeStepIndex ); } return {}; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RigMswCenterLineCalculator::calculateMswWellPipeGeometryForTimeStep( const RigEclipseCaseData* eclipseCaseData, const RigSimWellData* wellResults, int timeStepIndex ) { if ( timeStepIndex >= 0 && !wellResults->hasAnyValidCells( timeStepIndex ) ) return {}; const RigWellResultFrame* wellFramePtr = nullptr; if ( timeStepIndex < 0 ) { wellFramePtr = wellResults->staticWellResultFrame(); } else { wellFramePtr = wellResults->wellResultFrame( timeStepIndex ); } const RigWellResultFrame& wellFrame = *wellFramePtr; const std::vector resultBranches = wellFrame.wellResultBranches(); std::vector wellBranches = mergeShortBranchesIntoLongBranches( resultBranches ); // Connect outlet segment of branches to parent branch for ( const auto& resultBranch : resultBranches ) { if ( resultBranch.branchResultPoints().empty() ) continue; const RigWellResultPoint firstResultPoint = resultBranch.branchResultPoints().front(); for ( auto& wellBranch : wellBranches ) { if ( wellBranch.m_branchId == resultBranch.ertBranchId() ) { if ( firstResultPoint.branchId() == resultBranch.ertBranchId() ) { // The first result point is on the same branch, use well head as outlet RigWellResultPoint outletResultPoint = wellFrame.wellHead(); auto gridAndCellIndex = std::make_pair( outletResultPoint.gridIndex(), outletResultPoint.cellIndex() ); wellBranch.m_segmentsWithGridCells[outletResultPoint.segmentId()].push_back( gridAndCellIndex ); } else { // The first result point on a different branch. Find the branch and add the grid cell for ( const auto& candidateResultBranch : wellBranches ) { if ( firstResultPoint.branchId() == candidateResultBranch.m_branchId ) { std::pair gridAndCellIndexForTieIn; for ( const auto& [segment, gridAndCellIndices] : candidateResultBranch.m_segmentsWithGridCells ) { if ( segment > firstResultPoint.segmentId() ) continue; if ( !gridAndCellIndices.empty() ) { gridAndCellIndexForTieIn = gridAndCellIndices.front(); } } wellBranch.m_segmentsWithGridCells[firstResultPoint.segmentId()].push_back( gridAndCellIndexForTieIn ); } } } } } } std::vector simWellBranches; for ( const auto& wellBranch : wellBranches ) { std::vector cellCenterCoords; std::vector cellCenterResultPoints; if ( wellBranch.m_branchId == 1 && !wellBranch.m_segmentsWithGridCells.empty() ) { const auto& [firstSegment, gridAndCellIndices] = *wellBranch.m_segmentsWithGridCells.begin(); if ( !gridAndCellIndices.empty() ) { const auto& [gridIndex, cellIndex] = gridAndCellIndices.front(); if ( gridIndex < eclipseCaseData->gridCount() && cellIndex < eclipseCaseData->grid( gridIndex )->cellCount() ) { const RigCell& cell = eclipseCaseData->grid( gridIndex )->cell( cellIndex ); cvf::Vec3d whStartPos = cell.faceCenter( cvf::StructGridInterface::NEG_K ); // Add extra coordinate between cell face and cell center // to make sure the well pipe terminated in a segment parallel to z-axis cvf::Vec3d whIntermediate = whStartPos; whIntermediate.z() = ( whStartPos.z() + cell.center().z() ) / 2.0; RigWellResultPoint resPoint; for ( const auto& resBranch : resultBranches ) { for ( const auto& respoint : resBranch.branchResultPoints() ) { if ( respoint.segmentId() == firstSegment ) { resPoint = respoint; break; } } } cellCenterCoords.push_back( whStartPos ); cellCenterResultPoints.push_back( resPoint ); cellCenterCoords.push_back( whIntermediate ); cellCenterResultPoints.push_back( resPoint ); } } } for ( const auto& [segmentId, gridAndCellIndices] : wellBranch.m_segmentsWithGridCells ) { for ( const auto& [gridIndex, cellIndex] : gridAndCellIndices ) { if ( gridIndex < eclipseCaseData->gridCount() && cellIndex < eclipseCaseData->grid( gridIndex )->cellCount() ) { const RigCell& cell = eclipseCaseData->grid( gridIndex )->cell( cellIndex ); cvf::Vec3d pos = cell.center(); RigWellResultPoint resPoint; // The result point is only used to transport the grid index and cell index // The current implementation will propagate the cell open state to the well segment from one cell to // the next. resPoint.setGridIndex( gridIndex ); resPoint.setGridCellIndex( cellIndex ); auto gridAndCellIndex = std::make_pair( gridIndex, cellIndex ); if ( std::find( wellBranch.m_gridCellsConnectedToValve.begin(), wellBranch.m_gridCellsConnectedToValve.end(), gridAndCellIndex ) != wellBranch.m_gridCellsConnectedToValve.end() ) { resPoint.setIsConnectedToValve( true ); } cellCenterCoords.push_back( pos ); cellCenterResultPoints.push_back( resPoint ); } } } const auto simWellBranch = addCoordsAtCellFaceIntersectionsAndCreateBranch( cellCenterCoords, cellCenterResultPoints, eclipseCaseData ); simWellBranches.emplace_back( simWellBranch ); } return simWellBranches; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- SimulationWellCellBranch RigMswCenterLineCalculator::addCoordsAtCellFaceIntersectionsAndCreateBranch( const std::vector branchCoords, const std::vector& resultPoints, const RigEclipseCaseData* eclipseCaseData ) { std::vector adjustedCoords; std::vector adjustedResPoints; RigWellResultPoint previusPoint; for ( size_t resPointIdx = 0; resPointIdx < resultPoints.size(); resPointIdx++ ) { if ( resPointIdx >= branchCoords.size() ) continue; const auto& currentPoint = resultPoints[resPointIdx]; const auto& currentCellCenter = branchCoords[resPointIdx]; if ( previusPoint.isCell() ) { const RigCell& prevCell = eclipseCaseData->cellFromWellResultCell( previusPoint ); const cvf::Vec3d previousCellCenter = prevCell.center(); { // Insert extra coordinate at the location where the well path is leaving the previous cell cvf::Ray rayToThisCell; rayToThisCell.setOrigin( previousCellCenter ); rayToThisCell.setDirection( ( currentCellCenter - previousCellCenter ).getNormalized() ); cvf::Vec3d outOfPrevCell( previousCellCenter ); prevCell.firstIntersectionPoint( rayToThisCell, &outOfPrevCell ); if ( ( currentCellCenter - outOfPrevCell ).lengthSquared() > 1e-3 ) { adjustedCoords.push_back( outOfPrevCell ); adjustedResPoints.push_back( RigWellResultPoint() ); } } if ( currentPoint.isCell() ) { // Insert extra coordinate at the location where the well path is entering the current cell const RigCell& currentCell = eclipseCaseData->cellFromWellResultCell( currentPoint ); cvf::Ray rayFromThisCell; rayFromThisCell.setOrigin( currentCellCenter ); rayFromThisCell.setDirection( ( previousCellCenter - currentCellCenter ).getNormalized() ); cvf::Vec3d outOfCurrentCell( currentCellCenter ); currentCell.firstIntersectionPoint( rayFromThisCell, &outOfCurrentCell ); if ( ( currentCellCenter - outOfCurrentCell ).lengthSquared() > 1e-3 ) { adjustedCoords.push_back( outOfCurrentCell ); adjustedResPoints.push_back( currentPoint ); } } } adjustedResPoints.push_back( currentPoint ); adjustedCoords.push_back( currentCellCenter ); previusPoint = currentPoint; } // Duplicate last coord to make sure we have N+1 coordinates for N result points adjustedCoords.push_back( adjustedCoords.back() ); return { adjustedCoords, adjustedResPoints }; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RigMswCenterLineCalculator::mergeShortBranchesIntoLongBranches( const std::vector& resBranches ) { std::vector longWellBranches; std::vector shortWellBranches; for ( const auto& resultBranch : resBranches ) { WellBranch branch; branch.m_branchId = resultBranch.ertBranchId(); for ( const auto& resPoint : resultBranch.branchResultPoints() ) { size_t gridIndex = resPoint.gridIndex(); size_t gridCellIndex = resPoint.cellIndex(); auto gridAndCellIndex = std::make_pair( gridIndex, gridCellIndex ); if ( gridIndex != cvf::UNDEFINED_SIZE_T && gridCellIndex != cvf::UNDEFINED_SIZE_T && !branch.containsGridCell( gridAndCellIndex ) ) { OutputSegment outputSegment{ resPoint.outletSegmentId(), resPoint.outletBranchId() }; branch.m_gridCellsConnectedToSegments[gridAndCellIndex] = outputSegment; branch.m_segmentsWithGridCells[resPoint.segmentId()].push_back( gridAndCellIndex ); } } const int resultPointThreshold = 3; if ( resultBranch.branchResultPoints().size() > resultPointThreshold ) { longWellBranches.push_back( branch ); } else { shortWellBranches.push_back( branch ); } } // Move all grid cells of small branch to the long branch for ( const auto& branch : shortWellBranches ) { if ( branch.m_gridCellsConnectedToSegments.empty() ) continue; const auto& outputSegment = branch.m_gridCellsConnectedToSegments.begin()->second; for ( auto& longBranch : longWellBranches ) { if ( longBranch.m_branchId == outputSegment.outputSegmentBranchId ) { for ( const auto& [gridAndCellIndex, localOutputSegment] : branch.m_gridCellsConnectedToSegments ) { longBranch.m_segmentsWithGridCells[outputSegment.outputSegmentId].push_back( gridAndCellIndex ); longBranch.m_gridCellsConnectedToValve.push_back( gridAndCellIndex ); } } } } return longWellBranches; }