controller.hpp algorithm array cmath cstdint limits optional serif/utils/error/errors.hpp src/include/serif/solver/continuation/options.hpp serif::solver::continuation::CoordinateOptions serif::solver::continuation::CoordinateProposal serif::solver::continuation::CoordinateCounters serif::solver::continuation::CoordinateController serif serif::solver serif::solver::continuation #pragmaonce #include<algorithm> #include<array> #include<cmath> #include<cstdint> #include<limits> #include<optional> #include"serif/utils/error/errors.hpp" namespaceserif::solver::continuation{ enumclassCoordinateOrder{automatic,preferred,strict}; enumclassAttemptOutcome{success,recoverable_rejection,fatal_failure}; enumclassControllerStatus{ready,pending,complete,attempt_limit,no_progress,fatal_failure}; template<std::size_tCount> structCoordinateOptionsfinal{ static_assert(Count>0,"Continuationrequiresatleastonephysicalcoordinate."); std::array<double,Count>initial_step_fraction=filled(0.25); std::array<double,Count>minimum_step_fraction=filled(1.0e-6); std::array<double,Count>maximum_step_fraction=filled(1.0); std::array<std::size_t,Count>axis_order=[](){ std::array<std::size_t,Count>result{}; for(std::size_taxis=0;axis<Count;++axis){ result[axis]=axis; } returnresult; }(); CoordinateOrderorder{CoordinateOrder::automatic}; doublegrowth_factor{1.5}; doubleshrink_factor{0.5}; std::uint64_tmaximum_attempts{128}; std::uint64_tmaximum_rejections_per_axis{16}; [[nodiscard]]staticconstexprstd::array<double,Count>filled(constdoublevalue){ std::array<double,Count>result{}; result.fill(value); returnresult; } }; template<std::size_tCount> structCoordinateProposalfinal{ std::uint64_tid{}; std::size_taxis{}; std::array<double,Count>coordinates{}; doublestep_fraction{}; }; structCoordinateCountersfinal{ std::uint64_tattempts{}; std::uint64_tsuccesses{}; std::uint64_trejections{}; std::uint64_tfatal_failures{}; std::uint64_twork{}; }; template<std::size_tCount> classCoordinateControllerfinal{ public: usingCoordinates=std::array<double,Count>; usingProposal=CoordinateProposal<Count>; CoordinateController(Coordinatesstart,Coordinatestarget,CoordinateOptions<Count>options={}): m_start(start),m_target(target),m_options(options),m_accepted(start),m_step(options.initial_step_fraction){ validate_options(); for(std::size_taxis=0;axis<Count;++axis){ m_span[axis]=target[axis]-start[axis]; utils::errors::serif_ensure<utils::errors::SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>( std::isfinite(start[axis])&&std::isfinite(target[axis])&&std::isfinite(m_span[axis]), "Continuationendpointsandtheirdifferencesmustbefinite;invalidcoordinate{}.",axis); } if(m_accepted==m_target){ m_status=ControllerStatus::complete; } } [[nodiscard]]constCoordinates&start()constnoexcept{returnm_start;} [[nodiscard]]constCoordinates&target()constnoexcept{returnm_target;} [[nodiscard]]constCoordinates&accepted()constnoexcept{returnm_accepted;} [[nodiscard]]constCoordinateOptions<Count>&options()constnoexcept{returnm_options;} [[nodiscard]]constCoordinateCounters&counters()constnoexcept{returnm_counters;} [[nodiscard]]ControllerStatusstatus()constnoexcept{returnm_status;} [[nodiscard]]conststd::optional<Proposal>&pending()constnoexcept{returnm_pending;} [[nodiscard]]std::optional<Proposal>propose(){ utils::errors::serif_ensure<utils::errors::SERiFErrorCode::INVALID_ARGUMENT>(!m_pending, "Resolvetheoutstandingcontinuationproposalbeforerequestinganother."); if(m_status!=ControllerStatus::ready){ returnstd::nullopt; } if(m_counters.attempts==m_options.maximum_attempts){ m_status=ControllerStatus::attempt_limit; returnstd::nullopt; } for(std::size_tattempt=0;attempt<Count;++attempt){ conststd::optional<std::size_t>selected=select_axis(); if(!selected){ break; } conststd::size_taxis=*selected; Coordinatescoordinates=m_accepted; constdoubledistance=m_step[axis]*std::abs(m_span[axis]); if(distance>=std::abs(m_target[axis]-m_accepted[axis])){ coordinates[axis]=m_target[axis]; }else{ coordinates[axis]+=std::copysign(distance,m_span[axis]); } constdoubleactual=coordinates[axis]-m_accepted[axis]; if(!std::isfinite(coordinates[axis])||actual==0.0||std::signbit(actual)!=std::signbit(m_span[axis])){ m_blocked[axis]=true; continue; } if((m_span[axis]>0.0&&coordinates[axis]>m_target[axis])||(m_span[axis]<0.0&&coordinates[axis]<m_target[axis])){ coordinates[axis]=m_target[axis]; } ++m_counters.attempts; m_pending=Proposal{.id=m_counters.attempts,.axis=axis,.coordinates=coordinates, .step_fraction=std::abs((coordinates[axis]-m_accepted[axis])/m_span[axis])}; m_status=ControllerStatus::pending; returnm_pending; } m_status=ControllerStatus::no_progress; returnstd::nullopt; } voidvalidate_resolution(std::uint64_tid,AttemptOutcomeoutcome,std::uint64_twork=0)const{ usingnamespaceutils::errors; serif_ensure<SERiFErrorCode::INVALID_ARGUMENT>(m_pending&&m_status==ControllerStatus::pending&&m_pending->id==id, "Continuationresolutionmustidentifyitsoutstandingproposal."); serif_ensure<SERiFErrorCode::INVALID_ARGUMENT,std::invalid_argument>( outcome==AttemptOutcome::success||outcome==AttemptOutcome::recoverable_rejection||outcome==AttemptOutcome::fatal_failure, "Unknowncontinuationattemptoutcome."); serif_ensure<SERiFErrorCode::INVALID_ARGUMENT,std::overflow_error>(work<=std::numeric_limits<std::uint64_t>::max()-m_counters.work, "Continuationworkcounterwouldoverflow."); } voidresolve(std::uint64_tid,AttemptOutcomeoutcome,std::uint64_twork=0){ validate_resolution(id,outcome,work); conststd::size_taxis=m_pending->axis; constdoubleactual_fraction=m_pending->step_fraction; m_counters.work+=work; if(outcome==AttemptOutcome::success){ m_accepted=m_pending->coordinates; ++m_counters.successes; m_blocked.fill(false); m_rejections_at_base.fill(0); if(m_step[axis]>=m_options.maximum_step_fraction[axis]/m_options.growth_factor){ m_step[axis]=m_options.maximum_step_fraction[axis]; }else{ m_step[axis]*=m_options.growth_factor; } m_status=m_accepted==m_target?ControllerStatus::complete:ControllerStatus::ready; }elseif(outcome==AttemptOutcome::fatal_failure){ ++m_counters.fatal_failures; m_status=ControllerStatus::fatal_failure; }else{ ++m_counters.rejections; ++m_rejections_at_base[axis]; constdoubleprevious=std::min(m_step[axis],actual_fraction); constdoublenext=std::max(m_options.minimum_step_fraction[axis],previous*m_options.shrink_factor); if(m_rejections_at_base[axis]>=m_options.maximum_rejections_per_axis||actual_fraction<=m_options.minimum_step_fraction[axis]||next>=previous){ m_blocked[axis]=true; }else{ m_step[axis]=next; } m_status=ControllerStatus::ready; } m_pending.reset(); if(m_status==ControllerStatus::ready){ if(m_counters.attempts==m_options.maximum_attempts){ m_status=ControllerStatus::attempt_limit; }elseif(!select_axis()){ m_status=ControllerStatus::no_progress; } } } private: voidvalidate_options()const{ usingnamespaceutils::errors; constauto&options=m_options; serif_ensure<SERiFErrorCode::INVALID_ARGUMENT,std::invalid_argument>( options.order==CoordinateOrder::automatic||options.order==CoordinateOrder::preferred||options.order==CoordinateOrder::strict, "Unknowncontinuationcoordinateordering."); serif_ensure<SERiFErrorCode::INVALID_ARGUMENT,std::invalid_argument>( std::isfinite(options.growth_factor)&&options.growth_factor>=1.0&&std::isfinite(options.shrink_factor)&& options.shrink_factor>0.0&&options.shrink_factor<1.0&&options.maximum_attempts>0&&options.maximum_rejections_per_axis>0, "Continuationgrowthmustbeatleastone,shrinkagebetweenzeroandone,andworklimitspositive."); std::array<bool,Count>seen{}; for(std::size_taxis=0;axis<Count;++axis){ serif_ensure<SERiFErrorCode::INVALID_ARGUMENT,std::invalid_argument>(options.axis_order[axis]<Count&&!seen[options.axis_order[axis]], "Continuationaxisorderingmustbeapermutationoftheselectedcoordinates."); seen[options.axis_order[axis]]=true; serif_ensure<SERiFErrorCode::INVALID_ARGUMENT,std::invalid_argument>( std::isfinite(options.minimum_step_fraction[axis])&&std::isfinite(options.initial_step_fraction[axis])&& std::isfinite(options.maximum_step_fraction[axis])&&options.minimum_step_fraction[axis]>0.0&& options.minimum_step_fraction[axis]<=options.initial_step_fraction[axis]&& options.initial_step_fraction[axis]<=options.maximum_step_fraction[axis]&&options.maximum_step_fraction[axis]<=1.0, "Continuationfractionsmustsatisfy0<minimum<=initial<=maximum<=1,includingcoordinate{}.",axis); } } [[nodiscard]]std::optional<std::size_t>select_axis()constnoexcept{ std::optional<std::size_t>selected; doublelargest=-1.0; for(conststd::size_taxis:m_options.axis_order){ if(m_accepted[axis]==m_target[axis]){ continue; } if(m_options.order==CoordinateOrder::strict){ returnm_blocked[axis]?std::nullopt:std::optional<std::size_t>{axis}; } if(m_blocked[axis]){ continue; } if(m_options.order==CoordinateOrder::preferred){ returnaxis; } constdoubleremaining=std::abs((m_target[axis]-m_accepted[axis])/m_span[axis]); if(remaining>largest){ largest=remaining; selected=axis; } } returnselected; } Coordinatesm_start,m_target; CoordinateOptions<Count>m_options; Coordinatesm_accepted{},m_span{},m_step{}; std::array<bool,Count>m_blocked{}; std::array<std::uint64_t,Count>m_rejections_at_base{}; CoordinateCountersm_counters{}; ControllerStatusm_status{ControllerStatus::ready}; std::optional<Proposal>m_pending; }; }