///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2025- 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. // ///////////////////////////////////////////////////////////////////////////////// #pragma once #include "RigRefinement.h" #include "cafVecIjk.h" #include "cvfArray.h" #include #include #include #include #include #include #include #include class RimEclipseView; class RimEclipseCase; class RigModelPaddingSettings; class RigSimulationInputSettings; class RifOpmFlowDeckFile; class RigSimWellData; template class RigBoundingBoxIjk; class RigGridExportAdapter; class RigMainGrid; namespace Opm { class DeckRecord; class DeckKeyword; } // namespace Opm //================================================================================================== /// /// Tool for exporting simulation input files (sector models) /// //================================================================================================== class RigSimulationInputTool { public: // Data structures for NNC export /// Raw NNC connection with global cell indices struct NNCConnection { size_t c1GlobIdx; // Global cell index for cell 1 size_t c2GlobIdx; // Global cell index for cell 2 double transmissibility; // Transmissibility value }; /// Transformed NNC connection with sector-relative 0-based IJK coordinates struct TransformedNNCConnection { caf::VecIjk0 cell1 = caf::VecIjk0::ZERO; // 0-based sector IJK for cell 1 caf::VecIjk0 cell2 = caf::VecIjk0::ZERO; // 0-based sector IJK for cell 2 double transmissibility = 0.0; // Transmissibility value }; static std::expected exportSimulationInput( RimEclipseCase& eclipseCase, const RigSimulationInputSettings& settings, cvf::UByteArray* visibility ); static std::expected processEqualsRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const RigRefinement& refinement ); static std::expected processMultiplyRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const RigRefinement& refinement ); static std::expected processBoxRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const RigRefinement& refinement ); static std::expected processCopyRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const RigRefinement& refinement ); static std::expected processAddRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const RigRefinement& refinement ); static std::expected processAquconRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const RigRefinement& refinement ); static std::expected processAquanconRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const RigRefinement& refinement ); static std::expected processAqunumRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const RigRefinement& refinement ); // Penetration direction of well connections, keyed on (well name, original 0-based I, J, K) using ConnectionDirectionMap = std::map, RigRefinement::Dimension>; static ConnectionDirectionMap connectionDirections( const std::vector& compdatKeywords ); // Refined connections along the penetration direction are split into one record per sub-cell with // explicit CF and KH scaled by the sub-cell length fraction. Perpendicular directions use the center sub-cell. static std::expected, QString> processCompdatRecord( const Opm::DeckRecord& record, const std::string& wellName, const RigSimulationInputSettings& settings ); // Segment records are split into one record per sub-cell along the connection penetration direction, // with the MD interval divided by the sub-cell length fractions static std::expected, QString> processCompsegsRecord( const Opm::DeckRecord& record, const std::string& wellName, bool isWellNameRecord, const RigSimulationInputSettings& settings, const ConnectionDirectionMap& directions ); static std::vector createRefinedVisibility( const RigGridExportAdapter& gridAdapter ); static std::expected transformKeywordsInDeckFile( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); // Public EDITNNC methods for testing static std::vector extractDeckEditNncConnections( RifOpmFlowDeckFile& deckFile, const RigMainGrid& mainGrid ); static std::vector filterInternalSectorConnections( const std::vector& allConnections, const RigMainGrid& mainGrid, const caf::VecIjk0& min, const caf::VecIjk0& max ); static std::expected transformNNCToSectorCoordinates( const NNCConnection& connection, const RigMainGrid& mainGrid, const caf::VecIjk0& min, const RigRefinement& refinement ); static std::vector refineEditNncConnection( const NNCConnection& connection, const RigMainGrid& mainGrid, const caf::VecIjk0& min, const RigRefinement& refinement ); // Generic helper for processing keywords with box indices using RecordProcessorFunc = std::function( const Opm::DeckRecord&, const caf::VecIjk0&, const caf::VecIjk0&, const RigRefinement& )>; private: static std::expected updateCornerPointGridInDeckFile( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); // Scale MINPV down by the maximum refinement factor so OPM Flow doesn't filter sub-cells // whose pore volume falls below the original threshold simply because refinement shrunk // them. Does nothing if MINPV is absent from the deck or the refinement is identity. static std::expected scaleMinpvForRefinement( const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); static std::expected replaceKeywordValuesInDeckFile( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile, const std::set& alreadyCropped = {} ); static std::set cropDataKeywordsInDeckFile( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); static std::expected updateWelldimsKeyword( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); static std::expected addBorderBoundaryConditions( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, cvf::ref visibility, RifOpmFlowDeckFile& deckFile ); static std::expected replaceAquconKeywordIndices( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); static std::expected replaceAquanconKeywordIndices( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); static std::expected replaceAqunumKeywordIndices( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); static std::expected addFaultsToDeckFile( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); static std::expected filterAndUpdateWellKeywords( std::set& validWellNames, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); static std::expected updateWellListKeywords( std::set& validWellNames, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); static std::set wellNamesToInclude( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings ); static std::expected exportEditNncKeyword( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); static std::expected applyModelPadding( RifOpmFlowDeckFile& deckFile, const RigModelPaddingSettings& paddingSettings ); // Utility: Transform global IJK to sector-relative coordinates with refinement static caf::VecIjk0 transformToSectorCoordinates( const caf::VecIjk0& globalIjk, const caf::VecIjk0& min, const RigRefinement& refinement ); static std::expected addOperNumRegionAndOperater( RifOpmFlowDeckFile& deckFile, const RigGridExportAdapter& gridAdapter, const std::vector& operNumResult, int operNumRegion, double porvMultiplier ); static std::vector findIntersectingWells( RimEclipseCase* eclipseCase, const caf::VecIjk0& min, const caf::VecIjk0& max ); static std::expected processWelspecsRecord( const Opm::DeckRecord& record, const std::string& wellName, const RigSimulationInputSettings& settings ); static std::expected replaceKeywordWithBoxIndices( const std::string& keywordName, RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile, RecordProcessorFunc processorFunc ); // Helper function to transform bounding box from global to sector coordinates // Returns bounding box with 0-based sector-relative coordinates static std::expected, QString> transformBoxToSectorCoordinates( const RigBoundingBoxIjk& inputBox, const caf::VecIjk0& sectorMin, const caf::VecIjk0& sectorMax, const RigRefinement& refinement, const QString& keywordName, const QString& recordIdentifier ); // Helper function to extract IJK coordinates from a deck record // Returns a VecIjk1 (1-based) constructed from values at the specified item indices static caf::VecIjk1 extractIjk( const Opm::DeckRecord& record, size_t indexI, size_t indexJ, size_t indexK ); };