///////////////////////////////////////////////////////////////////////////////// // // 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 "RimWellLogCalculatedCurve.h" #include "RiaDefines.h" #include "RiaLogging.h" #include "RigWellLogCurveData.h" #include "RimMainPlotCollection.h" #include "RimOilField.h" #include "RimPlot.h" #include "RimProject.h" #include "RimWellLogPlot.h" #include "RimWellLogPlotCollection.h" #include "RimWellLogTrack.h" #include "RimWellPathCollection.h" #include "RiuPlotCurve.h" #include "cafPdmUiComboBoxEditor.h" #include "cafPdmUiTreeOrdering.h" CAF_PDM_SOURCE_INIT( RimWellLogCalculatedCurve, "WellLogCalculatedCurve" ); //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- namespace caf { template <> void AppEnum::setUp() { addItem( RimWellLogCalculatedCurve::Operators::ADD, "ADD", "+" ); addItem( RimWellLogCalculatedCurve::Operators::SUBTRACT, "SUBTRACT", "-" ); addItem( RimWellLogCalculatedCurve::Operators::MULTIPLY, "MULTIPLY", "*" ); addItem( RimWellLogCalculatedCurve::Operators::DIVIDE, "DIVIDE", "/" ); setDefault( RimWellLogCalculatedCurve::Operators::SUBTRACT ); } template <> void AppEnum::setUp() { addItem( RimWellLogCalculatedCurve::DepthSource::FIRST, "FIRST", "First Curve" ); addItem( RimWellLogCalculatedCurve::DepthSource::SECOND, "SECOND", "Second Curve" ); addItem( RimWellLogCalculatedCurve::DepthSource::UNION, "UNION", "Union" ); setDefault( RimWellLogCalculatedCurve::DepthSource::FIRST ); } } // namespace caf //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimWellLogCalculatedCurve::RimWellLogCalculatedCurve() { CAF_PDM_InitObject( "Well Log Calculated Curve", ":/WellLogCurve16x16.png" ); CAF_PDM_InitFieldNoDefault( &m_operator, "Operator", "Operator" ); m_operator.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); CAF_PDM_InitFieldNoDefault( &m_depthSource, "DepthSource", "Depth Source" ); m_depthSource.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); CAF_PDM_InitFieldNoDefault( &m_firstWellLogCurve, "FirstWellLogCurve", "First Well Log Curve" ); CAF_PDM_InitFieldNoDefault( &m_secondWellLogCurve, "SecondWellLogCurve", "Second Well Log Curve" ); setNamingMethod( RiaDefines::ObjectNamingMethod::AUTO ); setDeletable( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimWellLogCalculatedCurve::~RimWellLogCalculatedCurve() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellLogCalculatedCurve::setOperator( Operators operatorValue ) { m_operator = operatorValue; if ( m_namingMethod() == RiaDefines::ObjectNamingMethod::AUTO ) { setAutomaticName(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellLogCalculatedCurve::setWellLogCurves( RimWellLogCurve* firstWellLogCurve, RimWellLogCurve* secondWellLogCurve ) { disconnectWellLogCurveChangedFromSlots( m_firstWellLogCurve ); disconnectWellLogCurveChangedFromSlots( m_secondWellLogCurve ); if ( firstWellLogCurve ) { m_firstWellLogCurve = firstWellLogCurve; connectWellLogCurveChangedToSlots( m_firstWellLogCurve ); } if ( secondWellLogCurve ) { m_secondWellLogCurve = secondWellLogCurve; connectWellLogCurveChangedToSlots( m_secondWellLogCurve ); } if ( m_namingMethod() == RiaDefines::ObjectNamingMethod::AUTO ) { setAutomaticName(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimWellLogCalculatedCurve::createCurveAutoName() { if ( !m_firstWellLogCurve() || !m_secondWellLogCurve() ) return QString( "Not able to find source curves for calculated curve" ); const auto& operatorStr = m_operator().uiText(); return QString( "Calculated (%1 %2 %3)" ).arg( m_firstWellLogCurve->curveName() ).arg( operatorStr ).arg( m_secondWellLogCurve->curveName() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellLogCalculatedCurve::onLoadDataAndUpdate( bool updateParentPlot ) { if ( !m_firstWellLogCurve() || !m_secondWellLogCurve() ) return; if ( m_namingMethod() == RiaDefines::ObjectNamingMethod::AUTO ) { setAutomaticName(); } // Use well A as reference for resampled curve data auto* firstCurveData = m_firstWellLogCurve()->curveData(); auto* secondCurveData = m_secondWellLogCurve()->curveData(); if ( !firstCurveData || !secondCurveData ) return; if ( firstCurveData->depthUnit() != secondCurveData->depthUnit() ) { RiaLogging::warning( "Well log curve depth units are not the same" ); } if ( firstCurveData->propertyValueUnit() != secondCurveData->propertyValueUnit() ) { RiaLogging::warning( "Well log curve property value units are not the same" ); } // Find the data for resampling const auto depthType = RiaDefines::DepthTypeEnum::MEASURED_DEPTH; const std::vector depthValues = depthValuesFromSource( depthType ); const auto depthUnit = firstCurveData->depthUnit(); const auto propertyUnit = firstCurveData->propertyValueUnit(); auto firstCurveDepthValues = firstCurveData->depths( depthType ); auto firstCurvePropertyValues = firstCurveData->propertyValues(); auto secondCurveDepthValues = secondCurveData->depths( depthType ); auto secondCurvePropertyValues = secondCurveData->propertyValues(); // Resample curve depth and property values if needed if ( m_depthSource() != DepthSource::FIRST ) { cvf::ref firstCurveDataResampled = firstCurveData->calculateResampledCurveData( depthType, depthValues ); firstCurveDepthValues = firstCurveDataResampled->depths( depthType ); firstCurvePropertyValues = firstCurveDataResampled->propertyValues(); } if ( m_depthSource() != DepthSource::SECOND ) { cvf::ref secondCurveDataResampled = secondCurveData->calculateResampledCurveData( depthType, depthValues ); secondCurveDepthValues = secondCurveDataResampled->depths( depthType ); secondCurvePropertyValues = secondCurveDataResampled->propertyValues(); } // Verify equal sizes if ( firstCurveDepthValues.size() != depthValues.size() ) return; if ( firstCurveDepthValues.size() != firstCurvePropertyValues.size() ) return; if ( firstCurveDepthValues.size() != secondCurveDepthValues.size() ) return; if ( firstCurveDepthValues.size() != secondCurvePropertyValues.size() ) return; // Calculate curve std::vector calculatedDepthValues( depthValues.size() ); std::vector calculatedPropertyValues( depthValues.size() ); for ( size_t i = 0; i < depthValues.size(); ++i ) { calculatedPropertyValues[i] = calculateValue( firstCurvePropertyValues[i], secondCurvePropertyValues[i], m_operator() ); calculatedDepthValues[i] = depthValues[i]; } const bool useLogarithmicScale = false; const bool isExtractionCurve = false; // Set curve data auto depthsMap = std::map>(); depthsMap[depthType] = calculatedDepthValues; setPropertyValuesAndDepths( calculatedPropertyValues, depthsMap, 0.0, depthUnit, isExtractionCurve, useLogarithmicScale, propertyUnit ); // Set curve data to plot std::vector xPlotValues = curveData()->propertyValuesByIntervals(); std::vector yPlotValues = curveData()->depthValuesByIntervals( depthType, depthUnit ); m_plotCurve->setSamplesFromXValuesAndYValues( xPlotValues, yPlotValues, useLogarithmicScale ); updateCurvePresentation( updateParentPlot ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellLogCalculatedCurve::setAutomaticName() { m_curveName = createCurveAutoName(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellLogCalculatedCurve::onWellLogCurveChanged( const SignalEmitter* emitter ) { loadDataAndUpdate( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellLogCalculatedCurve::connectWellLogCurveChangedToSlots( RimWellLogCurve* wellLogCurve ) { if ( !wellLogCurve ) return; wellLogCurve->dataChanged.connect( this, &RimWellLogCalculatedCurve::onWellLogCurveChanged ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellLogCalculatedCurve::disconnectWellLogCurveChangedFromSlots( RimWellLogCurve* wellLogCurve ) { if ( !wellLogCurve ) return; wellLogCurve->dataChanged.disconnect( this ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RimWellLogCalculatedCurve::calculateValue( double firstValue, double secondValue, Operators operatorValue ) { if ( operatorValue == Operators::ADD ) { return firstValue + secondValue; } if ( operatorValue == Operators::SUBTRACT ) { return firstValue - secondValue; } if ( operatorValue == Operators::MULTIPLY ) { return firstValue * secondValue; } if ( operatorValue == Operators::DIVIDE ) { return secondValue == 0.0 ? std::numeric_limits::infinity() : firstValue / secondValue; } return 0.0; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimWellLogCalculatedCurve::depthValuesFromSource( RiaDefines::DepthTypeEnum depthType ) const { if ( !m_firstWellLogCurve || !m_firstWellLogCurve->curveData() || !m_secondWellLogCurve || !m_secondWellLogCurve->curveData() ) { return {}; } if ( m_depthSource() == DepthSource::FIRST ) { return m_firstWellLogCurve->curveData()->depths( depthType ); } else if ( m_depthSource() == DepthSource::SECOND ) { return m_secondWellLogCurve->curveData()->depths( depthType ); } else if ( m_depthSource() == DepthSource::UNION ) { return unionDepthValuesFromCurves( depthType ); } return {}; } //-------------------------------------------------------------------------------------------------- /// As this method creates an union of depths from two vectors, the duplicated depths are neglected. /// The duplicated depths in a depth vector represent enter and exist of a k-layer. When resampling /// the curves using the union of depths, one does not know if the new depth values are at enter /// or exit of a layer. Therefore, the duplicated depths are neglected. //-------------------------------------------------------------------------------------------------- std::vector RimWellLogCalculatedCurve::unionDepthValuesFromVectors( const std::vector& firstDepths, const std::vector& secondDepths, double threshold ) { auto isWithinThreshold = [&]( double value, const std::vector& existingValues ) -> bool { auto calculateDistance = []( double value1, double value2 ) -> double { return std::abs( value1 - value2 ); }; for ( const auto& existingValue : existingValues ) { if ( calculateDistance( value, existingValue ) < threshold ) { return true; } } return false; }; // Get union of depths std::vector unionDepths; auto insertIntoUnionDepthsVector = [&]( const std::vector& depths, std::vector& unionDepthsVector ) { for ( const auto& depth : depths ) { if ( isWithinThreshold( depth, unionDepthsVector ) ) continue; unionDepthsVector.push_back( depth ); } }; insertIntoUnionDepthsVector( firstDepths, unionDepths ); insertIntoUnionDepthsVector( secondDepths, unionDepths ); // Sort vector std::sort( unionDepths.begin(), unionDepths.end() ); return unionDepths; } //-------------------------------------------------------------------------------------------------- /// When creating an union depth vector, the duplicated depth values are neglected. These duplicates /// indicates enter and exit of k-layer. However, when curves are resampled, this info is not /// representative and duplicated depth values are not needed. //-------------------------------------------------------------------------------------------------- std::vector RimWellLogCalculatedCurve::unionDepthValuesFromCurves( RiaDefines::DepthTypeEnum depthType ) const { if ( !m_firstWellLogCurve || !m_firstWellLogCurve->curveData() || !m_secondWellLogCurve || !m_secondWellLogCurve->curveData() ) { return {}; } constexpr double depthThreshold = 0.1; // Get union of depths const auto firstDepths = m_firstWellLogCurve->curveData()->depths( depthType ); const auto secondDepths = m_secondWellLogCurve->curveData()->depths( depthType ); return unionDepthValuesFromVectors( firstDepths, secondDepths, depthThreshold ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimWellLogCalculatedCurve::wellName() const { return m_curveName; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimWellLogCalculatedCurve::wellLogChannelUiName() const { return m_curveName; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimWellLogCalculatedCurve::wellLogChannelUnits() const { if ( m_firstWellLogCurve->wellLogChannelUnits() != m_secondWellLogCurve->wellLogChannelUnits() ) { const auto& operatorStr = m_operator().uiText(); return QString( "%1 %2 %3" ) .arg( m_firstWellLogCurve->wellLogChannelUnits() ) .arg( operatorStr ) .arg( m_secondWellLogCurve->wellLogChannelUnits() ); } return m_firstWellLogCurve->wellLogChannelUnits(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellLogCalculatedCurve::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { RimPlotCurve::updateFieldUiState(); caf::PdmUiGroup* group = uiOrdering.addNewGroup( "Data Source" ); group->add( &m_depthSource ); group->add( &m_firstWellLogCurve ); group->add( &m_secondWellLogCurve ); group->add( &m_operator ); RimStackablePlotCurve::defaultUiOrdering( uiOrdering ); uiOrdering.skipRemainingFields( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellLogCalculatedCurve::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) { uiTreeOrdering.skipRemainingChildren( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimWellLogCalculatedCurve::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { RimWellLogCurve::fieldChangedByUi( changedField, oldValue, newValue ); if ( changedField == &m_firstWellLogCurve || changedField == &m_secondWellLogCurve ) { if ( !m_firstWellLogCurve() || !m_secondWellLogCurve() ) return; PdmObjectHandle* prevValue = oldValue.value>().rawPtr(); auto* prevWellLogCurve = dynamic_cast( prevValue ); disconnectWellLogCurveChangedFromSlots( prevWellLogCurve ); if ( changedField == &m_firstWellLogCurve ) connectWellLogCurveChangedToSlots( m_firstWellLogCurve ); if ( changedField == &m_secondWellLogCurve ) connectWellLogCurveChangedToSlots( m_secondWellLogCurve ); loadDataAndUpdate( true ); } if ( changedField == &m_operator || changedField == &m_depthSource ) { loadDataAndUpdate( true ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimWellLogCalculatedCurve::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) { QList options; options = RimWellLogCurve::calculateValueOptions( fieldNeedingOptions ); if ( !options.empty() ) return options; if ( fieldNeedingOptions == &m_firstWellLogCurve || fieldNeedingOptions == &m_secondWellLogCurve ) { RimWellLogPlotCollection* wellLogPlotCollection = RimMainPlotCollection::current()->wellLogPlotCollection(); if ( !wellLogPlotCollection ) return {}; // Find each well log plot in collection std::vector wellLogCurves = wellLogPlotCollection->descendantsOfType(); for ( RimWellLogCurve* curve : wellLogCurves ) { if ( !curve || curve == this ) continue; options.push_back( caf::PdmOptionItemInfo( curve->curveName(), curve ) ); } } return options; }