details.hpp array type_traits tuple expected serif/dimensions/runtime/concepts.hpp serif/eos/exceptions.hpp serif/eos/evaluation.hpp serif/eos/relations/catalog.hpp serif/utils/error/codes.hpp src/compile_time_checks/serif/eos/runtime/details.cpp src/include/serif/eos/runtime/concepts.hpp serif::eos::runtime::HasRuntimeQuantityIdentifiers serif::eos::runtime::HasRuntimeQuantityIdentifiers< relations::Relation< OutputT, InputTs... > > serif::eos::runtime::RuntimeRelationQuantities< relations::Relation< OutputT, InputTs... > > serif::eos::runtime::RuntimeRelationsAreSupported serif::eos::runtime::RuntimeRelationsAreSupported< true, RelationTs... > serif::eos::runtime::RuntimeCatalogIsSupported serif::eos::runtime::RuntimeCatalogIsSupported< relations::RelationCatalog< RelationTs... > > serif::eos::runtime::RuntimeRelationStorage< EOS, relations::Relation< OutputT, InputTs... > > serif::eos::runtime::RuntimeCatalogStorage< EOS, relations::RelationCatalog< RelationTs... > > serif::eos::runtime::RuntimeCatalogDispatch< EOS, relations::RelationCatalog< RelationTs... > > serif serif::eos serif::eos::runtime File containing implementation details for the eos runtime module. These details are used internally by the runtime system and are not intended to be used directly by users. Emily M. Boudreaux #pragmaonce #include<array> #include<type_traits> #include<tuple> #include<expected> #include"serif/dimensions/runtime/concepts.hpp" #include"serif/eos/exceptions.hpp" #include"serif/eos/evaluation.hpp" #include"serif/eos/relations/catalog.hpp" #include"serif/utils/error/codes.hpp" namespaceserif::eos::runtime{ template<typenameRelationT> structHasRuntimeQuantityIdentifiers:std::false_type{}; template<typenameOutputT,typename...InputTs> structHasRuntimeQuantityIdentifiers<relations::Relation<OutputT,InputTs...>> :std::bool_constant< dimensions::runtime::RuntimeIdentifiedThermodynamicQuantity<OutputT>&& (dimensions::runtime::RuntimeIdentifiedThermodynamicQuantity<InputTs>&&...)>{}; template<typenameRelationT> structRuntimeRelationQuantities; template<typenameOutputT,typename...InputTs> structRuntimeRelationQuantities<relations::Relation<OutputT,InputTs...>>{ usingType=std::tuple<OutputT,InputTs...>; }; template<typename...RelationTs> usingRuntimeCatalogQuantityTuple=decltype(std::tuple_cat(std::declval<typenameRuntimeRelationQuantities<RelationTs>::Type>()...)); template<typenameFirstQuantityT,typenameSecondQuantityT> [[nodiscard]]constevalboolruntimeQuantityIdentifiersAreCompatible(){ ifconstexpr(std::same_as<FirstQuantityT,SecondQuantityT>){ returntrue; }else{ returndimensions::runtime::thermodynamicQuantityID<FirstQuantityT>!= dimensions::runtime::thermodynamicQuantityID<SecondQuantityT>; } } template<typenameQuantityTupleT,std::size_tFirst,std::size_t...Offsets> [[nodiscard]]constevalboolruntimeQuantityIdentifierIsUnambiguous(std::index_sequence<Offsets...>){ return( runtimeQuantityIdentifiersAreCompatible< std::tuple_element_t<First,QuantityTupleT>, std::tuple_element_t<First+1+Offsets,QuantityTupleT>>()&&... ); } template<typenameQuantityTupleT,std::size_t...Indices> [[nodiscard]]constevalboolruntimeQuantityIdentifiersAreUnambiguous(std::index_sequence<Indices...>){ return( runtimeQuantityIdentifierIsUnambiguous<QuantityTupleT, Indices>(std::make_index_sequence<std::tuple_size_v<QuantityTupleT>-Indices-1>{})&&... ); } template<boolQuantitiesAreIdentified,typename...RelationTs> structRuntimeRelationsAreSupported:std::false_type{}; template<typename...RelationTs> structRuntimeRelationsAreSupported<true,RelationTs...>: std::bool_constant<runtimeQuantityIdentifiersAreUnambiguous<RuntimeCatalogQuantityTuple<RelationTs...>>( std::make_index_sequence<std::tuple_size_v<RuntimeCatalogQuantityTuple<RelationTs...>>>{} )>{}; template<typenameCatalogT> structRuntimeCatalogIsSupported:std::false_type{}; template<typename...RelationTs> structRuntimeCatalogIsSupported<relations::RelationCatalog<RelationTs...>>: RuntimeRelationsAreSupported<(HasRuntimeQuantityIdentifiers<RelationTs>::value&&...),RelationTs...>{}; template<typenameEOS,typenameRelationT> structRuntimeRelationStorage; template<typenameEOS,typenameOutputT,typename...InputTs> structRuntimeRelationStorage<EOS,relations::Relation<OutputT,InputTs...>>{ usingRelationType=relations::Relation<OutputT,InputTs...>; static_assert(sizeof...(InputTs)<=64,"RuntimeEOSrelationdescriptorssupportatmost64inputs.IfyouneedmorethanthisyouwillneedtoimplementyourowncustomRuntimeRelationStoragespecialization"); staticconstexprstd::array<dimensions::runtime::ThermodynamicQuantityID,sizeof...(InputTs)>inputQuantityIDs{ dimensions::runtime::thermodynamicQuantityID<InputTs>... }; template<std::size_t...Indices> [[nodiscard]]staticconstevalstd::uint64_tmakePartialDerivativeMask(std::index_sequence<Indices...>){ usingInputTuple=std::tuple<InputTs...>; /*Thisisperhapsoverly"clever"andself-indulgent. * *InsteadofchangingthatIwillprovideabriefexplanationofwhatthisisdoing. *Effectivelyweareaccumulatingabitmask.std::uint64_tontheleftinitializesthebitmasktoall0s *Thenwefoldoveralltheindicesoftheinputquantities * *AsareminderifyouhavenotrunintoanyoftheotherfoldingcommentsIhaveleftscatteredinthecodebase.C++17introducedthefoldingoperator, *whatwehavehereiswhatiscalledarightbinaryfold.Essentiallywestartwithavaluethenrepeatedlylogicallyoritwiththeresultofaniteration *overthetypepackIndices(whichissomelistofintegersfrom0tosizeof...(InputTs)-1).The|...|iswhatindicatesthis,thefirstpipeshowsweor *theinitialstate(a64bitintegerwithallbitssetto0)withtheresultofthefirstiteration,thenweorthatresultwiththeresultoftheseconditeration,andsoonuntilwehaveiteratedoverallindices. *ThereispersistentconfusionIfeelwiththissincewedonothavealoopvariableasastandardforloopwould;however,thisissimplythesyntaxavailable *inC++(asofC++23).TheloopingisimplicitinsofarasthecompilerimplicitlygeneratestheloopoverIndices. * *Weusestd::tuple_element_ttogetthetypeoftheinputquantityatthecurrentindex(recallthatthefoldloopgoesoverallindices) *Soforexampleifthetupleis<int,float,int,int>andweareattheseconditerationofthefoldingloop(index1)thenstd::tuple_element_twillevaluatetofloat *ThisiscontrivedthoughsincewearenotstoringprimitivesinthetuplebutrathertypesderivedfromtheThermodynamicQuantityconcept. * *Notewhereweevaluatethistype.InthetemplateargumentforEOSSupportingPartialDerivative.Basicallyweareaskingif,forthegivenEOS *andrelationtype,doesitsupportapartialderivativewithrespecttotheinputquantityatthecurrentindex.Thisevaluatesatcompile *timetoeithertrueorfalse.Thereisthenaternaryoperatorthatselectsa1(bitshiftedbythecurrentindextofitintherightbitmasklocation)ora0(whichwedon'tneedtobitshift) * *Whatthatmeansiswhenallissaidanddoneforaninputtypelistmadeofntypesthereisannbitinteger(paddedoutto64bits)whereeachbitcorresponds *towhethertheEOSsupportsapartialderivativewithrespecttotheinputquantityatthatindex.Thefirstinputquantitycorrespondstotheleastsignificantbitandthelastinputquantitycorrespondstothemostsignificantbit. */ return(std::uint64_t{0}|...|(models::EOSSupportingPartialDerivative<EOS, RelationType, std::tuple_element_t<Indices, InputTuple>>?(std::uint64_t{1}<<Indices):std::uint64_t{0})); } staticconstexprstd::uint64_tpartialDerivativeMask=makePartialDerivativeMask(std::index_sequence_for<InputTs...>{}); staticconstexprdimensions::runtime::RuntimeRelationDescriptordescriptor{ .outputQuantity=dimensions::runtime::thermodynamicQuantityID<OutputT>, .inputQuantities=std::span<constdimensions::runtime::ThermodynamicQuantityID>{inputQuantityIDs}, .partialDerivativeMask=partialDerivativeMask }; }; template<typenameEOS,typenameCatalogT> structRuntimeCatalogStorage; template<typenameEOS,typename...RelationTs> structRuntimeCatalogStorage<EOS,relations::RelationCatalog<RelationTs...>>{ staticconstexprstd::arraydescriptors{RuntimeRelationStorage<EOS, RelationTs>::descriptor...}; }; [[nodiscard]]inlinestd::expected<double,utils::errors::SERiFErrorCode>runtimeEvaluationFailure( constutils::errors::SERiFErrorCodecode ){ returnstd::unexpected{code}; } template<typenameEOS,typenameOutputT,typename...InputTs> [[nodiscard]]std::expected<double,utils::errors::SERiFErrorCode>evaluateRuntimeRelation( constEOS&eos, relations::Relation<OutputT,InputTs...>/*relation*/, conststd::span<constdimensions::runtime::RuntimeQuantityValue>inputValues ){ /*Onceagainwefindourselfatauseoffolding.Irecommendyoureadthecommentearlierinthisfileregardingfolding,orfindtheC++docsonfolding. * *ThegeneralpremisehereisthatwecalltheevaluationfunctionfortheEOSwiththegivenrelationandinputvalues.Wefoldoverallindicesusingthefinal...operator. *Thisisanimplicitloopgeneratedbythecompileroverthesocalled"parameterpack". */ constautoinvoke_evaluate=[&]<std::size_t...Indices>(std::index_sequence<Indices...>){ returneos::evaluate<OutputT>(eos,dimensions::QuantityValue<InputTs>{inputValues[Indices].value}...).value(); }; try{ returninvoke_evaluate(std::make_index_sequence<sizeof...(InputTs)>{}); }catch(constEOSEvaluationError&e){//Wemaywanttoreconsiderusingatry-catchforthis,butthisisgoodenoughfornow.RealisticallyifwewanttonotuseatrycatchwewillneedtochangethesignatureoftheEOSevaluationfunctiontoreturnastd::expectedinsteadofthrowinganexception.ThisisalargerchangethatIdon'twanttomakerightnow. returnstd::unexpected<utils::errors::SERiFErrorCode>{utils::errors::SERiFErrorCode::EOS_ERROR}; } } template<typenameInputQuantityT,typenameEOS,typenameOutputT,typename...InputTs> [[nodiscard]]booltryRuntimePartialDerivative( constEOS&eos, relations::Relation<OutputT,InputTs...>/*relation*/, constdimensions::runtime::ThermodynamicQuantityIDwithRespectTo, conststd::span<constdimensions::runtime::RuntimeQuantityValue>inputValues, std::expected<double,utils::errors::SERiFErrorCode>&result ){ if(withRespectTo!=dimensions::runtime::thermodynamicQuantityID<InputQuantityT>){ returnfalse; } ifconstexpr(models::EOSSupportingPartialDerivative<EOS,relations::Relation<OutputT,InputTs...>,InputQuantityT>){ constautoinvoke_evaluate_partial_derivative=[&]<std::size_t...Indices>(std::index_sequence<Indices...>){ returneos::partial_derivative<OutputT, InputQuantityT>(eos,dimensions::QuantityValue<InputTs>{inputValues[Indices].value}...).value(); }; try{ result=invoke_evaluate_partial_derivative(std::index_sequence_for<InputTs...>{}); }catch(constEOSEvaluationError&/*e*/){ result=std::unexpected{utils::errors::SERiFErrorCode::EOS_ERROR}; } }else{ result=runtimeEvaluationFailure(utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_DERIVATIVE); } returntrue; } template<typenameEOS,typenameOutputT,typename...InputTs> [[nodiscard]]std::expected<double,utils::errors::SERiFErrorCode>evaluateRuntimePartialDerivative( constEOS&eos, relations::Relation<OutputT,InputTs...>relation, constdimensions::runtime::ThermodynamicQuantityIDwithRespectTo, conststd::span<constdimensions::runtime::RuntimeQuantityValue>inputValues ){ std::expected<double,utils::errors::SERiFErrorCode>result=runtimeEvaluationFailure( utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_DERIVATIVE ); [[maybe_unused]]constboolmatched=(tryRuntimePartialDerivative<InputTs>(eos,relation,withRespectTo,inputValues,result)||...);//Herewefoldoverallinputandtrytofindonethatwecanevaluate. returnresult; } template<typenameEOS,typenameRelationT> [[nodiscard]]boolruntimeRelationMatches( constdimensions::runtime::ThermodynamicQuantityIDoutputQuantityID, conststd::span<constdimensions::runtime::RuntimeQuantityValue>inputValues ){ constdimensions::runtime::RuntimeRelationDescriptor&descriptor=RuntimeRelationStorage<EOS, RelationT>::descriptor; if(descriptor.outputQuantity!=outputQuantityID||descriptor.inputQuantities.size()!=inputValues.size()){ returnfalse; } for(std::size_ti=0;i<inputValues.size();++i){ if(descriptor.inputQuantities[i]!=inputValues[i].id){ returnfalse; } } returntrue; } template<typenameEOS,typenameCatalogT> structRuntimeCatalogDispatch; template<typenameEOS,typename...RelationTs> structRuntimeCatalogDispatch<EOS,relations::RelationCatalog<RelationTs...>>{ [[nodiscard]]staticstd::expected<double,utils::errors::SERiFErrorCode>evaluate( constvoid*object, constdimensions::runtime::ThermodynamicQuantityID&outputQuantity, conststd::span<constdimensions::runtime::RuntimeQuantityValue>inputValues ){ constauto&eos=*static_cast<constEOS*>(object);//Notsureifpolymorphictypeerasureistherighttoolhere.Itworksbutwemaywanttorevisititifitprecentcompileroptimizations.Thisdoeshoweverletuspassanyeosmodeltotheruntimeviewwithoutneedingtoknowthetypeatcompiletime(whichisthereforehelpfulwhenbuildingextensionsystemsinotherlanguageslikepython).Further,itmaymakesensetotaketheperformancehithereandusesomethinglikeadynamic_casttoensurethattheobjectisactuallyofthecorrecttype.Thiswouldbearuntimecheckbutitwouldbeamorerobustcheckthanjustblindlycastingtotheexpectedtype. std::expected<double,utils::errors::SERiFErrorCode>result=runtimeEvaluationFailure( utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_RELATION ); /*Foldoverallrelationsinthecatalogandcheckifanyofthemmatchtherequestedoutputquantityandinputquantities.Ifthematchisfoundthen *evaluatetherelationandreturntheresultandsetmatchedtotrue,ifnomatchisfoundthensetmatchedtofalse. */ [[maybe_unused]]constboolmatched=((runtimeRelationMatches<EOS, RelationTs>(outputQuantity,inputValues)?(result=evaluateRuntimeRelation(eos,RelationTs{},inputValues),true):false)||...); returnresult; } [[nodiscard]]staticstd::expected<double,utils::errors::SERiFErrorCode>partial_derivative( constvoid*object, constdimensions::runtime::ThermodynamicQuantityID&outputQuantity, constdimensions::runtime::ThermodynamicQuantityID&withRespectTo, conststd::span<constdimensions::runtime::RuntimeQuantityValue>inputValues ){ constauto&eos=*static_cast<constEOS*>(object);//SameCommentasabove std::expected<double,utils::errors::SERiFErrorCode>result=runtimeEvaluationFailure( utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_RELATION ); [[maybe_unused]]constboolmatched=((runtimeRelationMatches<EOS, RelationTs>(outputQuantity,inputValues)?(result=evaluateRuntimePartialDerivative(eos,RelationTs{},withRespectTo,inputValues),true):false)||...); returnresult; } }; }