///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2018- 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 "RiaSCurveCalculator.h" #include "RiaOffshoreSphericalCoords.h" #include "cvfMatrix4.h" #include #include //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiaSCurveCalculator::RiaSCurveCalculator( cvf::Vec3d p1, double azi1, double inc1, double rad1, cvf::Vec3d p2, double azi2, double inc2, double rad2 ) : m_isCalculationOK( false ) , m_p1( p1 ) , m_p2( p2 ) , m_firstArcEndpoint( p1 + 0.3 * ( p2 - p1 ) ) , m_secondArcStartpoint( p1 + 0.6 * ( p2 - p1 ) ) , m_r1( rad1 ) , m_r2( rad2 ) , m_ctrlPpointCurveStatus( NOT_SET ) , m_solveStatus( NOT_SOLVED ) { initializeByFinding_q1q2( p1, azi1, inc1, rad1, p2, azi2, inc2, rad2 ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiaSCurveCalculator::RiaSCurveCalculator( cvf::Vec3d p1, cvf::Vec3d q1, cvf::Vec3d p2, cvf::Vec3d q2 ) : m_isCalculationOK( true ) , m_p1( p1 ) , m_p2( p2 ) , m_ctrlPpointCurveStatus( NOT_SET ) , m_solveStatus( NOT_SOLVED ) { using Vec3d = cvf::Vec3d; bool isOk = true; m_isCalculationOK = true; Vec3d tq1q2 = ( q2 - q1 ).getNormalized( &isOk ); // !ok means the control points are in the same place. Could // fallback to use only one circle segment + one line. m_isCalculationOK = m_isCalculationOK && isOk; Vec3d t1 = ( q1 - p1 ).getNormalized( &isOk ); // !ok means no tangent specified. Could fallback to use only one // circle segment + one line. m_isCalculationOK = m_isCalculationOK && isOk; Vec3d t2 = ( p2 - q2 ).getNormalized( &isOk ); // !ok means no tangent specified. Could fallback to use only one // circle segment + one line or only one straight line if both // tangents are missing m_isCalculationOK = m_isCalculationOK && isOk; if ( !m_isCalculationOK ) { m_ctrlPpointCurveStatus = FAILED_INPUT_OVERLAP; } { Vec3d td1 = ( tq1q2 - t1 ); double td1Length = td1.length(); if ( td1Length > 1e-10 ) { td1 /= td1Length; m_c1 = q1 + ( ( q1 - p1 ).length() / ( td1 * ( -t1 ) ) ) * td1; m_r1 = ( m_c1 - p1 ).length(); } else // both control points are along t1. First curve has infinite radius { m_c1 = cvf::Vec3d::UNDEFINED; m_r1 = std::numeric_limits::infinity(); if ( m_ctrlPpointCurveStatus == NOT_SET ) { m_ctrlPpointCurveStatus = OK_INFINITE_RADIUS1; } } } { Vec3d td2 = ( -tq1q2 + t2 ); double td2Length = td2.length(); if ( td2Length > 1e-10 ) { td2 /= td2Length; m_c2 = q2 + ( ( q2 - p2 ).length() / ( td2 * ( t2 ) ) ) * td2; m_r2 = ( m_c2 - p2 ).length(); } else // both control points are along t2. Second curve has infinite radius { m_c2 = cvf::Vec3d::UNDEFINED; m_r2 = std::numeric_limits::infinity(); if ( m_ctrlPpointCurveStatus == NOT_SET ) { m_ctrlPpointCurveStatus = OK_INFINITE_RADIUS2; } else if ( m_ctrlPpointCurveStatus == OK_INFINITE_RADIUS1 ) { m_ctrlPpointCurveStatus = OK_INFINITE_RADIUS12; } } } m_firstArcEndpoint = q1 + ( q1 - p1 ).length() * tq1q2; m_secondArcStartpoint = q2 - ( q2 - p2 ).length() * tq1q2; if ( ( ( q1 - p1 ).length() + ( q2 - p2 ).length() ) > ( q2 - q1 ).length() ) // first arc end and second arc start // is overlapping { m_ctrlPpointCurveStatus = FAILED_ARC_OVERLAP; m_isCalculationOK = false; } if ( m_ctrlPpointCurveStatus == NOT_SET ) { m_ctrlPpointCurveStatus = OK; } // The Circle normals. Will be set to cvf::Vec3d::ZERO if undefined. m_n1 = ( t1 ^ tq1q2 ).getNormalized(); m_n2 = ( tq1q2 ^ t2 ).getNormalized(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiaSCurveCalculator RiaSCurveCalculator::fromTangentsAndLength( cvf::Vec3d p1, double azi1, double inc1, double lengthToQ1, cvf::Vec3d p2, double azi2, double inc2, double lengthToQ2 ) { cvf::Vec3d t1( RiaOffshoreSphericalCoords::unitVectorFromAziInc( azi1, inc1 ) ); cvf::Vec3d t2( RiaOffshoreSphericalCoords::unitVectorFromAziInc( azi2, inc2 ) ); cvf::Vec3d Q1 = p1 + lengthToQ1 * t1; cvf::Vec3d Q2 = p2 - lengthToQ2 * t2; RiaSCurveCalculator curveFromControlPoints( p1, Q1, p2, Q2 ); return curveFromControlPoints; } //-------------------------------------------------------------------------------------------------- /// /// Needs to calculate J^-1 * [R1_error, R2_error] /// | dR1_dq1 dR1_dq2 | 1 | dR2_dq2 -dR1_dq2 | /// J = | | J^-1 = ---------------------------------- | | /// | dR2_dq1 dR2_dq2 | dR1_dq1*dR2_dq2 - dR1_dq2*dR2_dq1 | -dR2_dq1 dR1_dq1 | /// /// | q1_step | | R1_Error | /// | | = - J^-1 | | /// | q2_step | | R2_Error | /// //-------------------------------------------------------------------------------------------------- void calculateNewStepsFromJacobi( double dR1_dq1, double dR1_dq2, double dR2_dq1, double dR2_dq2, double R1_error, double R2_error, double* newStepq1, double* newStepq2 ) { double invJacobiScale = 1.0 / ( dR1_dq1 * dR2_dq2 - dR2_dq1 * dR1_dq2 ); double invJacobi_R1q1 = invJacobiScale * dR2_dq2; double invJacobi_R1q2 = invJacobiScale * -dR1_dq2; double invJacobi_R2q1 = invJacobiScale * -dR2_dq1; double invJacobi_R2q2 = invJacobiScale * dR1_dq1; ( *newStepq1 ) = -( invJacobi_R1q1 * R1_error + invJacobi_R1q2 * R2_error ); ( *newStepq2 ) = -( invJacobi_R2q1 * R1_error + invJacobi_R2q2 * R2_error ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool isZeroCrossing( double newError, double oldError, double maxError ) { if ( ( newError < -maxError && maxError < oldError ) || ( newError > maxError && -maxError > oldError ) ) { return true; } return false; } //-------------------------------------------------------------------------------------------------- /// /// Iterating with changing q1, q2 (lengths along tangent) to find solution with R1 = r1 and R2 = r2 /// R1(q1, q2), R2(q1, q2) /// //-------------------------------------------------------------------------------------------------- void RiaSCurveCalculator::initializeByFinding_q1q2( cvf::Vec3d p1, double azi1, double inc1, double r1, cvf::Vec3d p2, double azi2, double inc2, double r2 ) { // Algorithm options const int maxIterations = 40; const double maxError = 0.01; const double maxStepSize = 1.0e9; const double maxLengthToQ = 1.0e10; bool enableBackstepping = false; //#define USE_JACOBI_UPDATE //#define DEBUG_OUTPUT_ON // Needs the initial partial derivatives to see the direction of change // dR1/dq1, dR1/dq2, dR2/dq1, dR2/dq2 // Selects a sensible point in the domain for the evaluation double p1p2Length = ( p2 - p1 ).length(); double delta = 0.01 * p1p2Length; double initialq1q2 = 0.2 * p1p2Length; double deltaPos = initialq1q2 + delta; RiaSCurveCalculator ev_0 = RiaSCurveCalculator::fromTangentsAndLength( p1, azi1, inc1, initialq1q2, p2, azi2, inc2, initialq1q2 ); if ( ev_0.curveStatus() == RiaSCurveCalculator::OK_INFINITE_RADIUS12 ) { *this = ev_0; this->m_solveStatus = CONVERGED; return; } // Todo: Handle infinite radius in one place RiaSCurveCalculator ev_dq1 = RiaSCurveCalculator::fromTangentsAndLength( p1, azi1, inc1, deltaPos, p2, azi2, inc2, initialq1q2 ); RiaSCurveCalculator ev_dq2 = RiaSCurveCalculator::fromTangentsAndLength( p1, azi1, inc1, initialq1q2, p2, azi2, inc2, deltaPos ); // Initial Jacobi double dR1_dq1 = ( ( r1 - ev_dq1.firstRadius() ) - ( r1 - ev_0.firstRadius() ) ) / delta; double dR2_dq2 = ( ( r2 - ev_dq2.secondRadius() ) - ( r2 - ev_0.secondRadius() ) ) / delta; // Initial function value (error) double R1_error = r1 - ev_0.firstRadius(); double R2_error = r2 - ev_0.secondRadius(); // First steps double q1Step = -R1_error / dR1_dq1; double q2Step = -R2_error / dR2_dq2; #ifdef USE_JACOBI_UPDATE double dR1_dq2 = ( ( r1 - ev_dq2.firstRadius() ) - ( r1 - ev_0.firstRadius() ) ) / delta; double dR2_dq1 = ( ( r2 - ev_dq1.secondRadius() ) - ( r2 - ev_0.secondRadius() ) ) / delta; calculateNewStepsFromJacobi( dR1_dq1, dR1_dq2, dR2_dq1, dR2_dq2, R1_error, R2_error, &q1Step, &q2Step ); #endif double q1 = initialq1q2; double q2 = initialq1q2; #ifdef DEBUG_OUTPUT_ON std::cout << std::endl; std::cout << "Targets: R1, R2: " << r1 << " , " << r2 << std::endl; std::cout << 0 << ": " << q1Step << " , " << q2Step << " : " << q1 << " , " << q2 << " | " << ev_0.isOk() << " : " << ev_0.firstRadius() << " , " << ev_0.secondRadius() << " : " << R1_error << " , " << R2_error << std::endl; #endif SolveStatus solveResultStatus = NOT_SOLVED; int backstepLevel = 0; int iteration = 1; for ( iteration = 1; iteration < maxIterations; ++iteration ) { if ( fabs( q1Step ) > maxStepSize || fabs( q2Step ) > maxStepSize ) { solveResultStatus = FAILED_MAX_TANGENT_STEP_REACHED; break; } std::string q1R1StepCorrMarker; std::string q2R2StepCorrMarker; if ( q1 + q1Step < 0 ) { q1Step = -0.9 * q1; q1R1StepCorrMarker = "*"; } if ( q2 + q2Step < 0 ) { q2Step = -0.9 * q2; q2R2StepCorrMarker = "*"; } q1 += q1Step; q2 += q2Step; if ( fabs( q1 ) > maxLengthToQ || fabs( q2 ) > maxLengthToQ ) { /// Max length along tangent reached solveResultStatus = FAILED_MAX_LENGTH_ALONG_TANGENT_REACHED; break; } RiaSCurveCalculator ev_1 = RiaSCurveCalculator::fromTangentsAndLength( p1, azi1, inc1, q1, p2, azi2, inc2, q2 ); double R1_error_new = r1 - ev_1.firstRadius(); double R2_error_new = r2 - ev_1.secondRadius(); #ifdef DEBUG_OUTPUT_ON std::cout << iteration << ": " << q1Step << q1R1StepCorrMarker << " , " << q2Step << q2R2StepCorrMarker << " : " << q1 << " , " << q2 << " | " << ev_1.isOk() << " : " << ev_1.firstRadius() << " , " << ev_1.secondRadius() << " : " << R1_error_new << " , " << R2_error_new; #endif if ( ( fabs( R1_error_new ) < maxError || ev_1.curveStatus() == OK_INFINITE_RADIUS1 ) && ( fabs( R2_error_new ) < maxError || ev_1.curveStatus() == OK_INFINITE_RADIUS2 ) ) { ev_0 = ev_1; // Result ok ! solveResultStatus = CONVERGED; #ifdef DEBUG_OUTPUT_ON std::cout << std::endl; #endif break; } if ( enableBackstepping ) // Experimental back-stepping { bool isZeroCrossingR1 = isZeroCrossing( R1_error_new, R1_error, maxError ); bool isZeroCrossingR2 = isZeroCrossing( R2_error_new, R2_error, maxError ); if ( isZeroCrossingR2 || isZeroCrossingR1 ) { q1 -= q1Step; q2 -= q2Step; // if (isZeroCrossingR1) q1Step = 0.9 * q1Step * fabs( R1_error ) / ( fabs( R1_error_new ) + fabs( R1_error ) ); // if (isZeroCrossingR2) q2Step = 0.9 * q2Step * fabs( R2_error ) / ( fabs( R2_error_new ) + fabs( R2_error ) ); ++backstepLevel; #ifdef DEBUG_OUTPUT_ON std::cout << " Backstep needed. " << std::endl; #endif continue; } else { backstepLevel = 0; } } #ifdef DEBUG_OUTPUT_ON std::cout << std::endl; #endif #ifdef USE_JACOBI_UPDATE /// Update Jacobi using Broyden // (R_error_n-Rerror_n-1) - Jn-1*dq // J_n = Jn-1 + --------------------------------- (dq)T // | dqn |^2 // double dR1_error = R1_error_new - R1_error; double dR2_error = R2_error_new - R2_error; R1_error = R1_error_new; R2_error = R2_error_new; double stepNormScale = 1.0 / ( q1Step * q1Step + q2Step * q2Step ); dR1_dq1 = dR1_dq1 + stepNormScale * ( q1Step * ( dR1_error - q1Step * dR1_dq1 + q2Step * dR1_dq2 ) ); dR1_dq2 = dR1_dq2 + stepNormScale * ( q2Step * ( dR1_error - q1Step * dR1_dq1 + q2Step * dR1_dq2 ) ); dR2_dq1 = dR2_dq1 + stepNormScale * ( q1Step * ( dR2_error - q1Step * dR2_dq1 + q2Step * dR2_dq2 ) ); dR2_dq2 = dR2_dq2 + stepNormScale * ( q2Step * ( dR2_error - q1Step * dR2_dq1 + q2Step * dR2_dq2 ) ); calculateNewStepsFromJacobi( dR1_dq1, dR1_dq2, dR2_dq1, dR2_dq2, R1_error, R2_error, &q1Step, &q2Step ); #else dR1_dq1 = ( ( r1 - ev_1.firstRadius() ) - ( r1 - ev_0.firstRadius() ) ) / q1Step; dR2_dq2 = ( ( r2 - ev_1.secondRadius() ) - ( r2 - ev_0.secondRadius() ) ) / q2Step; R1_error = R1_error_new; R2_error = R2_error_new; q1Step = -R1_error / dR1_dq1; q2Step = -R2_error / dR2_dq2; #endif ev_0 = ev_1; } *this = ev_0; if ( iteration >= maxIterations ) { m_solveStatus = FAILED_MAX_ITERATIONS_REACHED; // Max iterations reached } else { m_solveStatus = solveResultStatus; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiaSCurveCalculator::dump() const { cvf::Vec3d v_C1 = firstCenter(); cvf::Vec3d v_C2 = secondCenter(); cvf::Vec3d v_N1 = firstNormal(); cvf::Vec3d v_N2 = secondNormal(); cvf::Vec3d v_P11 = firstArcEndpoint(); cvf::Vec3d v_P22 = secondArcStartpoint(); std::cout << " P1: " << "[ " << m_p1[0] << " " << m_p1[1] << " " << m_p1[2] << " " << std::endl; std::cout << " P11: " << "[ " << v_P11[0] << " " << v_P11[1] << " " << v_P11[2] << " " << std::endl; std::cout << " P22: " << "[ " << v_P22[0] << " " << v_P22[1] << " " << v_P22[2] << " " << std::endl; std::cout << " P2: " << "[ " << m_p2[0] << " " << m_p2[1] << " " << m_p2[2] << " " << std::endl; std::cout << " C1: " << "[ " << v_C1[0] << " " << v_C1[1] << " " << v_C1[2] << " " << std::endl; std::cout << " C2: " << "[ " << v_C2[0] << " " << v_C2[1] << " " << v_C2[2] << " " << std::endl; std::cout << " N1: " << "[ " << v_N1[0] << " " << v_N1[1] << " " << v_N1[2] << " " << std::endl; std::cout << " N2: " << "[ " << v_N2[0] << " " << v_N2[1] << " " << v_N2[2] << " " << std::endl; std::cout << " R1: " << "[ " << firstRadius() << " ]" << std::endl; std::cout << " R2: " << "[ " << secondRadius() << " ]" << std::endl; std::cout << " CtrPointStatus: " << m_ctrlPpointCurveStatus << " SolveStatus: " << m_solveStatus << std::endl; }