solution.cpp serif/utils/laneemden/solution.hpp algorithm array cmath limits serif serif::utils serif::utils::laneemden #include"serif/utils/laneemden/solution.hpp" #include<algorithm> #include<array> #include<cmath> #include<limits> namespaceserif::utils::laneemden{ namespace{ Samplecentral_sample(doublen,doublexi,std::size_tterms){ std::array<double,64>coefficients{}; std::array<double,64>powers{}; coefficients[0]=1.0; powers[0]=1.0; for(std::size_torder=1;order<terms;++order){ if(order>=2){ coefficients[order]=-powers[order-2]/static_cast<double>(order*(order+1)); } doublesum=0.0; for(std::size_tk=1;k<=order;++k){ sum+=(static_cast<double>(k)*(n+1.0)-static_cast<double>(order))*coefficients[k]*powers[order-k]; } powers[order]=sum/static_cast<double>(order); } doubletheta=coefficients[terms-1]; doublederivative=0.0; for(std::size_torder=terms-1;order>0;--order){ derivative=derivative*xi+theta; theta=theta*xi+coefficients[order-1]; } return{.xi=xi,.theta=theta,.derivative=derivative}; } Sampleslope(doublen,constSample&sample){ constdoublesource=std::pow(std::max(0.0,sample.theta),n); return{.xi=1.0,.theta=sample.derivative,.derivative=-2.0*sample.derivative/sample.xi-source}; } Samplestage(constSample&initial,constSample&rate,doublestep){ return{ .xi=initial.xi+step, .theta=initial.theta+step*rate.theta, .derivative=initial.derivative+step*rate.derivative }; } Sampleadvance(doublen,constSample&initial,doublestep){ constSamplefirst=slope(n,initial); constSamplesecond=slope(n,stage(initial,first,0.5*step)); constSamplethird=slope(n,stage(initial,second,0.5*step)); constSamplefourth=slope(n,stage(initial,third,step)); return{ .xi=initial.xi+step, .theta=initial.theta+step*(first.theta+2.0*second.theta+2.0*third.theta+fourth.theta)/6.0, .derivative=initial.derivative+step*(first.derivative+2.0*second.derivative+2.0*third.derivative+fourth.derivative)/6.0 }; } boolvalid_interior(constSample&sample){ returnstd::isfinite(sample.xi)&&std::isfinite(sample.theta)&&std::isfinite(sample.derivative)&& sample.theta>0.0&&sample.derivative<0.0; } std::expected<Sample,std::string>locate_surface(doublen,constSample&initial,doublewidth,doubletolerance){ doublelower=0.0; doubleupper=width; Sampleresult=advance(n,initial,upper); if(!std::isfinite(result.theta)||result.theta>0.0){ returnstd::unexpected("Lane–Emdensurfacecouldnotbebracketed;reducetheintegrationstep."); } for(std::size_titeration=0;iteration<80&&upper-lower>tolerance;++iteration){ constdoublemiddle=lower+0.5*(upper-lower); result=advance(n,initial,middle); if(result.theta>0.0){ lower=middle; }else{ upper=middle; } } result=advance(n,initial,lower+0.5*(upper-lower)); result.theta=0.0; if(!std::isfinite(result.derivative)||result.derivative>=0.0){ returnstd::unexpected("Lane–Emdensurfacederivativeisnotfiniteandnegative;reducetheintegrationstep."); } returnresult; } } std::expected<Solution,std::string>solve(constdoublen,IntegrationOptionsoptions){ if(!std::isfinite(n)||n<0.0){ returnstd::unexpected("Lane–Emdenindexmustbefiniteandnonnegative."); } if(!std::isfinite(options.step)||options.step<=0.0||!std::isfinite(options.maximum_radius)||options.maximum_radius<=0.0|| !std::isfinite(options.surface_tolerance)||options.surface_tolerance<=0.0||options.surface_tolerance>=0.01|| options.maximum_steps==0||options.central_series_terms<6||options.central_series_terms>64){ returnstd::unexpected("Lane–Emdenintegrationneedspositivefinitestepandmaximumradius,asurfacetolerancein(0,0.01),anonzerostepbudget,and6to64seriesterms."); } Solutionsolution{.n=n,.samples={{.xi=0.0,.theta=1.0,.derivative=0.0}},.surface=std::nullopt}; constdoublefirst_radius=std::min({options.step,options.maximum_radius,0.01/std::sqrt(std::max(1.0,n))}); Samplecurrent=central_sample(n,first_radius,options.central_series_terms); if(!valid_interior(current)){ returnstd::unexpected("Lane–Emdencentralseriesisnotrepresentableforthesuppliedindexandstep."); } solution.samples.push_back(current); for(std::size_titeration=0;current.xi<options.maximum_radius;++iteration){ if(iteration>=options.maximum_steps){ returnstd::unexpected("Lane–Emdenintegrationexhaustedmaximum_stepsbeforereachingthesurfaceormaximum_radius."); } constdoubledistance=-current.theta/current.derivative; constdoubletolerance=options.surface_tolerance*std::max(1.0,current.xi); if(distance<=tolerance&&current.xi+2.0*distance<=options.maximum_radius){ constdoubleroot_tolerance=std::max(4.0*std::numeric_limits<double>::epsilon()*current.xi,tolerance*0.01); constautosurface=locate_surface(n,current,2.0*distance,root_tolerance); if(!surface){ returnstd::unexpected(surface.error()); } solution.surface=*surface; solution.samples.push_back(*surface); returnsolution; } //Shortenonlythefinalapproach;noacceptedsampleextendspastthefirstzero. constdoublestep=std::min({options.step,0.5*distance,options.maximum_radius-current.xi}); if(current.xi+step==current.xi){ returnstd::unexpected("Lane–Emdenstepcannotadvancetheradiusatmachineprecision;relaxsurface_tolerance."); } current=advance(n,current,step); if(!valid_interior(current)){ returnstd::unexpected("Lane–Emdenintegrationleftthefinitepositivebranch;reducetheintegrationstep."); } solution.samples.push_back(current); } returnsolution; } std::expected<Sample,std::string>evaluate(constSolution&solution,doublexi){ if(!std::isfinite(xi)||xi<0.0||solution.samples.empty()||xi>solution.samples.back().xi){ returnstd::unexpected("Lane–Emdenevaluationradiusmustliewithinthestoredfinitedimensionlessprofile."); } constautoupper=std::lower_bound(solution.samples.begin(),solution.samples.end(),xi, [](constSample&sample,doubleradius){returnsample.xi<radius;}); if(upper==solution.samples.begin()||upper->xi==xi){ return*upper; } constSample&left=*(upper-1); constSample&right=*upper; constdoublewidth=right.xi-left.xi; if(!std::isfinite(width)||width<=0.0){ returnstd::unexpected("Lane–Emdenevaluationrequiresaprofilewithincreasingfiniteradiiproducedbysolve."); } constdoubleposition=(xi-left.xi)/width; constdoublequadratic=3.0*(right.theta-left.theta)-width*(2.0*left.derivative+right.derivative); constdoublecubic=2.0*(left.theta-right.theta)+width*(left.derivative+right.derivative); returnSample{ .xi=xi, .theta=left.theta+position*(width*left.derivative+position*(quadratic+position*cubic)), .derivative=left.derivative+position*(2.0*quadratic+3.0*position*cubic)/width }; } }