///////////////////////////////////////////////////////////////////////////////// // // 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 "RigWellAllocationOverTime.h" #include "cafAssert.h" #include "RigAccWellFlowCalculator.h" #include "RigFlowDiagResultAddress.h" #include "RigWellResultPoint.h" #include //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigWellAllocationOverTime::RigWellAllocationOverTime( const std::vector& timeStepDates, const std::map& timeStepAndCalculatorPairs ) : m_timeStepDates( timeStepDates ) { for ( const auto& [date, calculator] : timeStepAndCalculatorPairs ) { std::string err = "Calculator for time step date " + date.toString().toStdString() + " does not exist in time step dates vector "; CAF_ASSERT( std::find( m_timeStepDates.begin(), m_timeStepDates.end(), date ) != m_timeStepDates.end() && err.data() ); } // Time steps not present in input map is considered "excluded" time steps // Build new time step and calculator map using calculator for "next" valid time step for // "excluded" time steps QDateTime prevValidTimeStep; for ( auto it = m_timeStepDates.rbegin(); it != m_timeStepDates.rend(); ++it ) { const QDateTime& timeStep = *it; auto timeStepCalculatorIt = timeStepAndCalculatorPairs.find( timeStep ); if ( timeStepCalculatorIt != timeStepAndCalculatorPairs.end() ) { m_timeStepAndCalculatorPairs.emplace( timeStep, timeStepCalculatorIt->second ); prevValidTimeStep = timeStep; } else if ( prevValidTimeStep.isValid() ) { // If no calculator for this time step, use the previous valid time step calculator m_timeStepAndCalculatorPairs.emplace( timeStep, timeStepAndCalculatorPairs.at( prevValidTimeStep ) ); } } std::sort( m_timeStepDates.begin(), m_timeStepDates.end() ); // Retrieve union of well names across all calculators std::set allWellNames; for ( const auto& [date, calculator] : m_timeStepAndCalculatorPairs ) { allWellNames.insert( calculator.tracerNames().begin(), calculator.tracerNames().end() ); } // Fill default well values into map const double defaultValue = 0.0; for ( const auto& well : allWellNames ) { for ( const auto& date : m_timeStepDates ) { std::pair defaultPair( date, defaultValue ); m_defaultWellValuesMap[well].insert( defaultPair ); } } m_wellValuesMap = m_defaultWellValuesMap; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RigWellAllocationOverTime::fillWithFlowRateFractionValues() { m_wellValuesMap = m_defaultWellValuesMap; for ( auto& [timeStep, calculator] : m_timeStepAndCalculatorPairs ) { const auto totalTracerFractions = calculator.totalTracerFractions(); for ( const auto& [wellName, value] : totalTracerFractions ) { m_wellValuesMap[wellName][timeStep] = value; } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RigWellAllocationOverTime::fillWithFlowRatePercentageValues() { m_wellValuesMap = m_defaultWellValuesMap; for ( auto& [timeStep, calculator] : m_timeStepAndCalculatorPairs ) { const auto totalTracerFractions = calculator.totalTracerFractions(); for ( const auto& [wellName, value] : totalTracerFractions ) { double valuePercent = 100.0 * value; m_wellValuesMap[wellName][timeStep] = valuePercent; } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RigWellAllocationOverTime::fillWithFlowRateValues() { m_wellValuesMap = m_defaultWellValuesMap; const size_t branchIdx = 0; for ( auto& [timeStep, calculator] : m_timeStepAndCalculatorPairs ) { for ( const auto& wellName : calculator.tracerNames() ) { const auto& accumulatedConnectionFlows = calculator.accumulatedTracerFlowPrConnection( wellName, branchIdx ); const double topConnectionFlow = accumulatedConnectionFlows.empty() ? 0.0 : accumulatedConnectionFlows.back(); m_wellValuesMap[wellName][timeStep] = topConnectionFlow; } } } //-------------------------------------------------------------------------------------------------- /// Fill with flow volume at time step. /// /// Create volume by multiplying with number of days since last time step. //-------------------------------------------------------------------------------------------------- void RigWellAllocationOverTime::fillWithFlowVolumeValues() { fillWithFlowRateValues(); for ( auto& [well, timeStepsAndValues] : m_wellValuesMap ) { QDateTime prevTimeStep; for ( auto& [timeStep, value] : timeStepsAndValues ) { if ( !prevTimeStep.isValid() ) { prevTimeStep = timeStep; continue; } const auto numDays = static_cast( prevTimeStep.daysTo( timeStep ) ); value = value * numDays; prevTimeStep = timeStep; } } } //-------------------------------------------------------------------------------------------------- /// Fill with accumulated flow volume over a range of time steps. Create volume by multiplying with /// number of days since last time step. /// /// Group small contributors in "Others" if accumulated volume value at last time step is below /// threshold value. //-------------------------------------------------------------------------------------------------- void RigWellAllocationOverTime::fillWithAccumulatedFlowVolumeValues( double smallContributionsThreshold ) { fillWithFlowRateValues(); for ( auto& [well, timeStepsAndValues] : m_wellValuesMap ) { QDateTime prevTimeStep; double accumulatedVolume = 0.0; for ( auto& [timeStep, value] : timeStepsAndValues ) { if ( !prevTimeStep.isValid() ) { prevTimeStep = timeStep; continue; } const auto numDays = static_cast( prevTimeStep.daysTo( timeStep ) ); const double volume = value * numDays; accumulatedVolume += volume; value = accumulatedVolume; prevTimeStep = timeStep; } } if ( smallContributionsThreshold > 0.0 ) { groupAccumulatedFlowVolumes( m_wellValuesMap, smallContributionsThreshold ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RigWellAllocationOverTime::fillWithAccumulatedFlowVolumeFractionValues( double smallContributionsThreshold ) { fillWithAccumulatedFlowVolumeFractionOrPercentageValues( FractionOrPercentage::FRACTION, smallContributionsThreshold ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RigWellAllocationOverTime::fillWithAccumulatedFlowVolumePercentageValues( double smallContributionsThreshold ) { fillWithAccumulatedFlowVolumeFractionOrPercentageValues( FractionOrPercentage::PERCENTAGE, smallContributionsThreshold ); } //-------------------------------------------------------------------------------------------------- /// Fill with accumulated well flow volumes in fraction/percent of total accumulated flow volume /// at each time step. /// /// /// Group small contributors in "Others" if volume value for well is below threshold at every /// time step. //-------------------------------------------------------------------------------------------------- void RigWellAllocationOverTime::fillWithAccumulatedFlowVolumeFractionOrPercentageValues( FractionOrPercentage selection, double smallContributionsThreshold ) { const double scaling = selection == FractionOrPercentage::FRACTION ? 1.0 : 100.0; // Handle threshold filtering afterwards const double nonFilteringThreshold = 0.0; fillWithAccumulatedFlowVolumeValues( nonFilteringThreshold ); for ( const auto& timeStep : m_timeStepDates ) { double totalAccumulatedVolume = 0.0; std::map timeStepWellValues; // Sum accumulated volumes at time step for ( auto& [well, values] : m_wellValuesMap ) { const auto accumulatedVolume = values[timeStep]; totalAccumulatedVolume += accumulatedVolume; timeStepWellValues[well] = accumulatedVolume; } // If no accumulated volume exist at time step if ( totalAccumulatedVolume == 0.0 ) continue; // Create percentage value for ( auto& [well, value] : timeStepWellValues ) { m_wellValuesMap[well][timeStep] = scaling * value / totalAccumulatedVolume; } } if ( smallContributionsThreshold > 0.0 ) { const auto threshold = scaling * smallContributionsThreshold; groupAccumulatedFlowVolumeFractionsOrPercentages( m_wellValuesMap, threshold ); } } //-------------------------------------------------------------------------------------------------- /// Handle grouping of small contributors in accumulated volume data based on threshold. /// Group small contributors in "Others" if accumulated volume value at last time step is below /// threshold value. //-------------------------------------------------------------------------------------------------- void RigWellAllocationOverTime::groupAccumulatedFlowVolumes( std::map>& rWellValuesMap, double threshold ) { if ( m_timeStepDates.empty() ) return; std::map> groupedWellValuesMap; std::map lastAccumulatedWellValues; double sumLastAccumulatedWellValues = 0.0; const QDateTime lastTimeStep = m_timeStepDates.back(); for ( auto& [well, values] : rWellValuesMap ) { const double lastWellValue = values[lastTimeStep]; lastAccumulatedWellValues[well] = lastWellValue; sumLastAccumulatedWellValues += lastWellValue; } // Filter out wells with accumulated flow less than threshold and place among "others" std::vector contributingWells; std::vector groupedWells; for ( const auto& [well, value] : lastAccumulatedWellValues ) { if ( sumLastAccumulatedWellValues > 0.0 && ( value / sumLastAccumulatedWellValues ) < threshold ) { groupedWells.push_back( well ); } else { contributingWells.push_back( well ); } } for ( const auto& well : contributingWells ) { groupedWellValuesMap[well] = rWellValuesMap[well]; } for ( const auto& well : groupedWells ) { if ( groupedWellValuesMap.count( RIG_TINY_TRACER_GROUP_NAME ) == 0 ) { groupedWellValuesMap[RIG_TINY_TRACER_GROUP_NAME] = rWellValuesMap[well]; } else { for ( const auto& [date, value] : rWellValuesMap[well] ) { groupedWellValuesMap[RIG_TINY_TRACER_GROUP_NAME][date] += value; } } } rWellValuesMap = groupedWellValuesMap; } //-------------------------------------------------------------------------------------------------- /// Handle grouping of small contributors in accumulated volume fraction/percentage based on threshold. /// Group small contributors in "Others" if fraction/percentage value for well is below threshold at every /// time step. If fraction/percentage value is above threshold for one time step or more, show data for well /// at every time step. //-------------------------------------------------------------------------------------------------- void RigWellAllocationOverTime::groupAccumulatedFlowVolumeFractionsOrPercentages( std::map>& rWellValuesMap, double threshold ) { auto getMaxValue = []( const std::map& valuesMap ) -> double { double maxValue = 0.0; for ( const auto& [timeStep, value] : valuesMap ) { maxValue = value > maxValue ? value : maxValue; } return maxValue; }; std::vector contributingWells; std::vector groupedWells; for ( const auto& [well, values] : rWellValuesMap ) { const double maxValue = getMaxValue( values ); if ( maxValue > threshold ) { contributingWells.push_back( well ); } else { groupedWells.push_back( well ); } } std::map> groupedWellValuesMap; for ( const auto& well : contributingWells ) { groupedWellValuesMap[well] = rWellValuesMap[well]; } for ( const auto& well : groupedWells ) { if ( groupedWellValuesMap.count( RIG_TINY_TRACER_GROUP_NAME ) == 0 ) { groupedWellValuesMap[RIG_TINY_TRACER_GROUP_NAME] = rWellValuesMap[well]; } else { for ( const auto& [date, value] : rWellValuesMap[well] ) { groupedWellValuesMap[RIG_TINY_TRACER_GROUP_NAME][date] += value; } } } rWellValuesMap = groupedWellValuesMap; }