family.hpp algorithm cmath functional limits optional tuple utility vector serif/solver/continuation/parameters.hpp serif/model/stellar_model.hpp serif/utils/error/errors.hpp src/include/serif/solver/continuation/continuation.hpp src/include/serif/solver/continuation/options.hpp src/include/serif/solver/continuation/polytropic.hpp serif::solver::continuation::ParameterLaw serif::solver::continuation::detail::Contained< ParameterList< Parameters... >, Superset > serif::solver::continuation::detail::FamilyTraits< ParameterList< Selected... >, std::tuple< Laws... > > serif::solver::continuation::detail::AllIdentified< utils::types::TypeList< Specifications... > > serif::solver::continuation::detail::CopyEquations serif::solver::continuation::BoundFamily< Model, ParameterList< Selected... >, std::tuple< Laws... >, EquationFactory > serif::solver::continuation::BoundFamily< Model, ParameterList< Selected... >, std::tuple< Laws... >, EquationFactory >::Values serif::solver::continuation::Family serif serif::solver serif::solver::continuation serif::solver::continuation::detail #pragmaonce #include<algorithm> #include<cmath> #include<functional> #include<limits> #include<optional> #include<tuple> #include<utility> #include<vector> #include"serif/solver/continuation/parameters.hpp" #include"serif/model/stellar_model.hpp" #include"serif/utils/error/errors.hpp" namespaceserif::solver::continuation{ template<PhysicalParameter...Parameters> [[nodiscard]]constexprautodepends_on(Parameters...)noexcept{returnParameterList<Parameters...>{};} template<PhysicalParameterOutput,typenameDependencies,typenameFunction> structParameterLawfinal{ usingOutputParameter=Output; usingDependencyParameters=Dependencies; Functionfunction; }; namespacedetail{ template<typenameFunction> [[nodiscard]]boolcallable_valid(constFunction&value){ ifconstexpr(std::is_pointer_v<Function>||std::is_member_pointer_v<Function>){ returnvalue!=nullptr; }else{ returntrue; } } [[nodiscard]]inlineboolvalues_agree(doublefirst,doublesecond)noexcept{ returnstd::isfinite(first)&&std::isfinite(second)&&(first==second|| std::abs(first-second)<=64.0*std::numeric_limits<double>::epsilon()*std::max(std::abs(first),std::abs(second))); } template<typenameList,typenameSuperset> structContained; template<typename...Parameters,typenameSuperset> structContained<ParameterList<Parameters...>,Superset>:std::bool_constant<(utils::dependencies::ContainsKey<Parameters,Superset>&&...)>{}; template<typenameCandidate> conceptLawDefinition=requires{ typenameCandidate::OutputParameter; typenameCandidate::DependencyParameters; requiresPhysicalParameter<typename Candidate::OutputParameter>; requiresutils::dependencies::IsKeyList<typename Candidate::DependencyParameters>; }; template<typenameSelected,typenameLaws> structFamilyTraits; template<typename...Selected,typename...Laws> structFamilyTraits<ParameterList<Selected...>,std::tuple<Laws...>>{ usingDerived=ParameterList<typenameLaws::OutputParameter...>; staticconstexprboolvalid=sizeof...(Selected)>0&&utils::dependencies::IsKeyList<ParameterList<Selected...>>&& utils::dependencies::IsKeyList<Derived>&& (!utils::dependencies::ContainsKey<typenameLaws::OutputParameter,ParameterList<Selected...>>&&...)&& (Contained<typenameLaws::DependencyParameters,ParameterList<Selected...>>::value&&...); staticconstexprboolcomplete=valid&&(Contained<typename RequiredDependents<Selected>::Type,Derived>::value&&...); template<typenameModel> staticconstexprboolowners_present=(Model::templatecontains_specification<typenameSelected::Specification>&&...)&& (Model::templatecontains_specification<typenameLaws::OutputParameter::Specification>&&...); template<typenameSpecification> staticconstexprboolaffects=(std::same_as<Specification,typenameSelected::Specification>||...)|| (std::same_as<Specification,typenameLaws::OutputParameter::Specification>||...); }; template<typenameSpecifications> structAllIdentified; template<typename...Specifications> structAllIdentified<utils::types::TypeList<Specifications...>>:std::bool_constant<(IdentifiedSpecification<Specifications>&&...)>{}; template<typenameModel,typenameSelected,typenameLaws> conceptBindableFamily=model::StellarModelType<Model>&&FamilyTraits<Selected, Laws>::complete&& FamilyTraits<Selected, Laws>::templateowners_present<Model>&&IdentifiedSpecification<typename Model::EquationOfState>&& AllIdentified<typename Model::SpecificationTypes>::value&& (std::is_empty_v<typenameModel::Equations>||IdentifiedSpecification<typename Model::Equations>); structCopyEquationsfinal{ template<typenameEquations> requiresstd::copy_constructible<Equations> [[nodiscard]]Equationsoperator()(constEquations&value)const{returnvalue;} }; template<typenameLaw,typenameModel> [[nodiscard]]autobind_law(Law&&value,constModel&target){ ifconstexpr(requires{std::forward<Law>(value).bind(target);}){ returnstd::forward<Law>(value).bind(target); }else{ returnstd::remove_cvref_t<Law>(std::forward<Law>(value)); } } } template<PhysicalParameterOutput,PhysicalParameter...Inputs,typenameFunction> requiresutils::dependencies::IsKeyList<ParameterList<Inputs...>>&& std::invocable<conststd::remove_cvref_t<Function>&,typenameInputs::Value...>&& std::same_as<std::remove_cvref_t<std::invoke_result_t<conststd::remove_cvref_t<Function>&,typenameInputs::Value...>>,typenameOutput::Value> [[nodiscard]]autolaw(Output,ParameterList<Inputs...>,Function&&function){ utils::errors::serif_ensure<utils::errors::SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(detail::callable_valid(function), "Acontinuationlawcannotuseanullcallablepointer."); returnParameterLaw<Output,ParameterList<Inputs...>,std::remove_cvref_t<Function>>{.function=std::forward<Function>(function)}; } template<typenameModel,typenameSelected,typenameLaws,typenameEquationFactory> classBoundFamily; template<model::StellarModelTypeModel,PhysicalParameter...Selected,typename...Laws,typenameEquationFactory> classBoundFamily<Model,ParameterList<Selected...>,std::tuple<Laws...>,EquationFactory>final{ usingSelection=ParameterList<Selected...>; usingTraits=detail::FamilyTraits<Selection,std::tuple<Laws...>>; usingDerivedValues=std::tuple<typenameLaws::OutputParameter::Value...>; public: usingModelType=Model; staticconstexprstd::size_tcoordinate_count=sizeof...(Selected); usingCoordinates=std::array<double,coordinate_count>; template<typenameParameter> staticconstexprboolhas_coordinate=utils::dependencies::ContainsKey<Parameter, Selection>; template<PhysicalParameterParameter> requireshas_coordinate<Parameter> [[nodiscard]]staticconstevalstd::size_tcoordinate_index(Parameter={}){ returnutils::dependencies::key_index<Parameter, Selection>(); } [[nodiscard]]staticconstexprstd::array<ParameterDescriptor,coordinate_count>parameter_descriptors()noexcept{ return{{{Selected::name,Selected::stable_id}...}}; } BoundFamily(Modeltarget,std::tuple<Laws...>laws,EquationFactoryequations): m_target(std::move(target)),m_laws(std::move(laws)),m_equations(std::move(equations)),m_target_coordinates(coordinates_of(m_target)){ static_assert(detail::BindableFamily<Model,Selection,std::tuple<Laws...>>); conststd::vector<double>identity=model_signature(m_target); constboolfinite=std::ranges::all_of(identity,[](doublevalue){returnstd::isfinite(value);}); utils::errors::serif_ensure<utils::errors::SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(finite, "Everyphysicalinputinacontinuationtargetidentitymustbefinite."); constautoendpoint=try_model_at(m_target_coordinates); utils::errors::serif_ensure<utils::errors::SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(endpoint.has_value(), "Thecontinuationtargetisinconsistent:{}",endpoint?std::string{}:endpoint.error().message); } [[nodiscard]]constModel&target_model()constnoexcept{returnm_target;} [[nodiscard]]Coordinatestarget_coordinates()constnoexcept{returnm_target_coordinates;} [[nodiscard]]Coordinatesstarting_coordinates()const{ constautostart=[&]<PhysicalParameterParameter>(){ autovalue=Parameter::read(m_target.templatespecification<typenameParameter::Specification>()); ifconstexpr(requires{{Parameter::starting_value(value)}->std::same_as<typenameParameter::Value>;}){ value=Parameter::starting_value(value); } utils::errors::serif_ensure<utils::errors::SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(Parameter::valid(value), "Thedefaultstarting{}liesoutsideitsphysicaldomain.",Parameter::name); returncoordinate_value(value); }; return{start.templateoperator()<Selected>()...}; } [[nodiscard]]Coordinatescoordinates_of(constModel&candidate)const{ return{coordinate_value(Selected::read(candidate.templatespecification<typenameSelected::Specification>()))...}; } [[nodiscard]]std::vector<double>model_signature(constModel&candidate)const{ std::vector<double>signature; constautoappend=[&](constauto&value){ constautoidentity=SpecificationTraits<std::remove_cvref_t<decltype(value)>>::identity(value); signature.push_back(static_cast<double>(std::ranges::size(identity))); signature.insert(signature.end(),std::ranges::begin(identity),std::ranges::end(identity)); }; candidate.visit_specifications(append); ifconstexpr(!std::is_empty_v<typenameModel::Equations>){ append(candidate.equations()); } returnsignature; } [[nodiscard]]std::expected<void,Failure>try_validate_member(constModel&candidate)const{ constautoexpected=try_model_at(coordinates_of(candidate)); if(!expected){ returnstd::unexpected(expected.error()); } constautoactual_identity=model_signature(candidate); constautoexpected_identity=model_signature(*expected); if(actual_identity.size()!=expected_identity.size()||!std::ranges::equal(actual_identity,expected_identity,detail::values_agree)){ returnstd::unexpected(Failure{.message="Thestartingmodeldoesnotbelongtothedeclaredphysicalfamily."}); } return{}; } voidvalidate_member(constModel&candidate)const{ constautoresult=try_validate_member(candidate); utils::errors::serif_ensure<utils::errors::SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(result.has_value(), "{}",result?std::string{}:result.error().message); } [[nodiscard]]std::expected<Model,Failure>try_model_at(constCoordinates&coordinates)const{ if(!(Selected::valid(typenameSelected::Value{coordinates[coordinate_index(Selected{})]})&&...)){ returnstd::unexpected(Failure{.message="Acontinuationcoordinateliesoutsideitsphysicalparameterdomain."}); } autoderived=evaluate_laws(coordinates,std::index_sequence_for<Laws...>{}); if(!derived){ returnstd::unexpected(derived.error()); } constboolendpoint=coordinates==m_target_coordinates; constValuesvalues{m_target,coordinates,*derived}; autocandidate=m_target.transform_specifications([&](constauto&specification){ usingSpecification=std::remove_cvref_t<decltype(specification)>; ifconstexpr(Traits::templateaffects<Specification>){ if(!endpoint){ returnSpecificationTraits<Specification>::rebuild(specification,values); } } returnSpecificationTraits<Specification>::clone(specification); },std::invoke(m_equations,m_target.equations())); if(endpoint&&model_signature(candidate)!=model_signature(m_target)){ returnstd::unexpected(Failure{.message="Acontinuationclonechangedtheexacttargetphysicalidentity."}); } if(!realized(candidate,coordinates,*derived,std::index_sequence_for<Laws...>{})){ returnstd::unexpected(Failure{.message="Acontinuationlaworprescriptiondidnotreproduceitsselectedandderivedphysicalparameters."}); } returncandidate; } [[nodiscard]]Modelmodel_at(constCoordinates&coordinates)const{ autoresult=try_model_at(coordinates); utils::errors::serif_ensure<utils::errors::SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(result.has_value(), "{}",result?std::string{}:result.error().message); returnstd::move(*result); } private: structValuesfinal{ constModel&target; constCoordinates&coordinates; constDerivedValues&derived; template<PhysicalParameterParameter> [[nodiscard]]typenameParameter::Valueget(Parameter)const{ ifconstexpr(has_coordinate<Parameter>){ returntypenameParameter::Value{coordinates[coordinate_index(Parameter{})]}; }elseifconstexpr(utils::dependencies::ContainsKey<Parameter, typename Traits::Derived>){ returnstd::get<utils::dependencies::key_index<Parameter,typenameTraits::Derived>()>(derived); }else{ returnParameter::read(target.templatespecification<typenameParameter::Specification>()); } } }; template<typenameLaw,typename...Inputs> [[nodiscard]]autoevaluate_law(constLaw&value,constCoordinates&coordinates,ParameterList<Inputs...>)const{ usingOutput=typenameLaw::OutputParameter; usingResult=std::expected<typenameOutput::Value,Failure>; if(!detail::callable_valid(value.function)){ returnResult{std::unexpected(Failure{.message="Acontinuationlawhasanullcallablepointer."})}; } autoresult=std::invoke(value.function,typenameInputs::Value{coordinates[coordinate_index(Inputs{})]}...); if(!Output::valid(result)){ returnResult{std::unexpected(Failure{.message="Adependentcontinuationlawreturnedaninvalidphysicalvalue."})}; } returnResult{std::move(result)}; } template<std::size_t...Indices> [[nodiscard]]std::expected<DerivedValues,Failure>evaluate_laws(constCoordinates&coordinates,std::index_sequence<Indices...>)const{ autoresults=std::tuple{evaluate_law(std::get<Indices>(m_laws),coordinates,typenameLaws::DependencyParameters{})...}; std::optional<Failure>failure; constautocheck=[&](constauto&result){ if(!result&&!failure){ failure=result.error(); } }; (check(std::get<Indices>(results)),...); if(failure){ returnstd::unexpected(std::move(*failure)); } returnDerivedValues{std::move(*std::get<Indices>(results))...}; } template<std::size_t...Indices> [[nodiscard]]boolrealized(constModel&result,constCoordinates&coordinates,constDerivedValues&derived,std::index_sequence<Indices...>)const{ return((coordinate_value(Selected::read(result.templatespecification<typenameSelected::Specification>()))==coordinates[coordinate_index(Selected{})])&&...)&& (detail::values_agree(coordinate_value(Laws::OutputParameter::read(result.templatespecification<typenameLaws::OutputParameter::Specification>())), coordinate_value(std::get<Indices>(derived)))&&...); } Modelm_target; std::tuple<Laws...>m_laws; EquationFactorym_equations; Coordinatesm_target_coordinates; }; template<typenameSelected,typenameLaws> classFamilyfinal{ public: explicitFamily(Lawslaws={}):m_laws(std::move(laws)){} template<detail::LawDefinition...Added> requiresdetail::FamilyTraits<Selected, decltype(std::tuple_cat(std::declval<Laws>(),std::declval<std::tuple<Added...>>()))>::valid [[nodiscard]]autowith(Added...laws)const&requiresstd::copy_constructible<Laws>{ returnFamily<Selected,decltype(std::tuple_cat(m_laws,std::tuple<Added...>{std::move(laws)...}))>{ std::tuple_cat(m_laws,std::tuple<Added...>{std::move(laws)...})}; } template<detail::LawDefinition...Added> requiresdetail::FamilyTraits<Selected, decltype(std::tuple_cat(std::declval<Laws>(),std::declval<std::tuple<Added...>>()))>::valid [[nodiscard]]autowith(Added...laws)&&{ autojoined=std::tuple_cat(std::move(m_laws),std::tuple<Added...>{std::move(laws)...}); returnFamily<Selected,decltype(joined)>{std::move(joined)}; } template<typenameModel,typenameEquationFactory=detail::CopyEquations> requiresdetail::BindableFamily<Model, Selected, Laws>&&std::copy_constructible<Laws>&& std::invocable<constEquationFactory&,consttypenameModel::Equations&> [[nodiscard]]autobind(Modeltarget,EquationFactoryfactory={})const&{ returnbind_impl(std::move(target),m_laws,std::move(factory)); } template<typenameModel,typenameEquationFactory=detail::CopyEquations> requiresdetail::BindableFamily<Model, Selected, Laws>&&std::invocable<constEquationFactory&,consttypenameModel::Equations&> [[nodiscard]]autobind(Modeltarget,EquationFactoryfactory={})&&{ returnbind_impl(std::move(target),std::move(m_laws),std::move(factory)); } private: template<typenameModel,typenameLawValues,typenameEquationFactory> [[nodiscard]]staticautobind_impl(Modeltarget,LawValues&&values,EquationFactoryfactory){ autobound=std::apply([&](auto&&...value){ returnstd::tuple{detail::bind_law(std::forward<decltype(value)>(value),target)...}; },std::forward<LawValues>(values)); returnBoundFamily<Model,Selected,decltype(bound),EquationFactory>{std::move(target),std::move(bound),std::move(factory)}; } Lawsm_laws; }; template<PhysicalParameter...Parameters> requires(sizeof...(Parameters)>0)&&utils::dependencies::IsKeyList<ParameterList<Parameters...>> [[nodiscard]]autoin(Parameters...){returnFamily<ParameterList<Parameters...>,std::tuple<>>{};} }