determinant_polynomial.cpp serif/geometry/deformation/detail/determinant_polynomial.hpp algorithm bit cmath limits numeric serif serif::geometry serif::geometry::deformation serif::geometry::deformation::detail #include"serif/geometry/deformation/detail/determinant_polynomial.hpp" #include<algorithm> #include<bit> #include<cmath> #include<limits> #include<numeric> namespaceserif::geometry::deformation::detail{ namespace{ [[nodiscard]]doubleselected_column_determinant(constJacobianEntries&base,constJacobianEntries&direction,constunsignedintdirection_columns,constintdimension,constdoublemaximum_step)noexcept{ constautoentry=[&](constintrow,constintcolumn){ if((direction_columns&(1U<<static_cast<unsignedint>(column)))!=0U){ returnmaximum_step*direction[row][column]; } returnbase[row][column]; }; if(dimension==1){ returnentry(0,0); } if(dimension==2){ returnentry(0,0)*entry(1,1)-entry(0,1)*entry(1,0); } returnentry(0,0)*(entry(1,1)*entry(2,2)-entry(1,2)*entry(2,1))- entry(0,1)*(entry(1,0)*entry(2,2)-entry(1,2)*entry(2,0))+ entry(0,2)*(entry(1,0)*entry(2,1)-entry(1,1)*entry(2,0)); } [[nodiscard]]intpolynomial_degree(constDeterminantPolynomial&coefficients)noexcept{ doublescale=0.0; for(constdoublecoefficient:coefficients){ scale=std::max(scale,std::abs(coefficient)); } constdoubletolerance=64.0*std::numeric_limits<double>::epsilon()*scale; for(intdegree=3;degree>0;--degree){ if(std::abs(coefficients[degree])>tolerance){ returndegree; } } return0; } voidappend_unit_interval_root(std::array<double,2>&roots,int&root_count,constdoubleroot)noexcept{ if(!std::isfinite(root)||root<=0.0||root>=1.0){ return; } if(root_count>0&&std::abs(root-roots[0])<=64.0*std::numeric_limits<double>::epsilon()){ return; } roots[root_count]=root; ++root_count; } [[nodiscard]]intderivative_critical_points(constDeterminantPolynomial&coefficients,constintdegree,std::array<double,2>&critical_points)noexcept{ intcount=0; if(degree==2){ append_unit_interval_root(critical_points,count,-coefficients[1]/(2.0*coefficients[2])); }elseif(degree==3){ constdoublequadratic=3.0*coefficients[3]; constdoublelinear=2.0*coefficients[2]; constdoubleconstant=coefficients[1]; constdoublediscriminant=std::fma(linear,linear,-4.0*quadratic*constant); constdoublediscriminant_scale=linear*linear+std::abs(4.0*quadratic*constant); constdoublediscriminant_tolerance=64.0*std::numeric_limits<double>::epsilon()*discriminant_scale; if(discriminant>=-discriminant_tolerance){ constdoublesquare_root=std::sqrt(std::max(0.0,discriminant)); if(square_root==0.0){ append_unit_interval_root(critical_points,count,-linear/(2.0*quadratic)); }else{ constdoubleq=-0.5*(linear+std::copysign(square_root,linear)); append_unit_interval_root(critical_points,count,q/quadratic); append_unit_interval_root(critical_points,count,constant/q); } } } std::sort(critical_points.begin(),critical_points.begin()+count); returncount; } [[nodiscard]]doublebisect_first_nonpositive_value(constDeterminantPolynomial&coefficients,doublelower,doubleupper)noexcept{ for(intiteration=0;iteration<80;++iteration){ constdoublemiddle=std::midpoint(lower,upper); if(evaluate_polynomial(coefficients,middle)>0.0){ lower=middle; }else{ upper=middle; } } returnupper; } } DeterminantPolynomialdeterminant_polynomial(constJacobianEntries&base,constJacobianEntries&direction,constintdimension,constdoublemaximum_step,constdoubledeterminant_floor)noexcept{ DeterminantPolynomialcoefficients{}; constunsignedintterm_count=1U<<static_cast<unsignedint>(dimension); for(unsignedintmask=0;mask<term_count;++mask){ coefficients[std::popcount(mask)]+=selected_column_determinant(base,direction,mask,dimension,maximum_step); } coefficients[0]-=determinant_floor; returncoefficients; } doubleevaluate_polynomial(constDeterminantPolynomial&coefficients,constdoubleparameter)noexcept{ returnstd::fma(parameter,std::fma(parameter,std::fma(parameter,coefficients[3],coefficients[2]),coefficients[1]),coefficients[0]); } doublefirst_boundary_parameter(constDeterminantPolynomial&input_coefficients)noexcept{ //Scalingleavesrootsandtherelativetolerancesunchanged,andpreventsoverflowinthecubicdiscriminant. doublescale=0.0; for(constdoublecoefficient:input_coefficients){ scale=std::max(scale,std::abs(coefficient)); } DeterminantPolynomialcoefficients{}; for(std::size_tindex=0;index<coefficients.size();++index){ coefficients[index]=input_coefficients[index]/scale; } constintdegree=polynomial_degree(coefficients); if(degree==0){ returnstd::numeric_limits<double>::infinity(); } doublecoefficient_scale=0.0; for(constdoublecoefficient:coefficients){ coefficient_scale+=std::abs(coefficient); } constdoublevalue_tolerance=128.0*std::numeric_limits<double>::epsilon()*coefficient_scale; std::array<double,2>critical_points{}; constintcritical_point_count=derivative_critical_points(coefficients,degree,critical_points); std::array<double,4>interval_ends{}; for(intindex=0;index<critical_point_count;++index){ interval_ends[index+1]=critical_points[index]; } interval_ends[critical_point_count+1]=1.0; for(intinterval=0;interval<=critical_point_count;++interval){ constdoublelower=interval_ends[interval]; constdoubleupper=interval_ends[interval+1]; constdoubleupper_value=evaluate_polynomial(coefficients,upper); if(upper_value<=0.0){ returnbisect_first_nonpositive_value(coefficients,lower,upper); } if(upper_value<=value_tolerance){ //Atangentzeromayevaluateafewulpsabovezeroatitsderivativeroot. returnupper; } } returnstd::numeric_limits<double>::infinity(); } }