using Formula_t = decltype (k * (_1 - r0) / (_1 + r0) * (g0 / (alpha0 - logr0 / Num<300>) - _1)); // const auto residual = Formula_t::Eval (datapoint) - knownValue; // // : const auto dg0 = VarDerivative_t<Formula_t, decltype (g0)>::Eval (datapoint); const auto dalpha0 = VarDerivative_t<Formula_t, decltype (alpha0)>::Eval (datapoint); const auto dk = VarDerivative_t<Formula_t, decltype (k)>::Eval (datapoint);
Node
type: template<typename NodeClass, typename... Args> struct Node;
NodeClass
is the node type (variable, number, unary function, binary function), Args
are the parameters of this node (variable index, value of number, child nodes). using NumberType_t = long long; template<NumberType_t N> struct Number {};
template<NumberType_t N> struct Node<Number<N>> { template<char FPrime, int IPrime> using Derivative_t = Node<Number<0>>; static std::string Print () { return std::to_string (N); } template<typename Vec> static typename Vec::value_type Eval (const Vec&) { return N; } constexpr Node () {} };
Derivative_t
is responsible for this, let’s leave its template parameters for now). Printing a number is also easy (see Print()
). Calculate a node with a number — return this number (see Eval()
, the Vec
template parameter will be discussed later). template<char Family, int Index> struct Variable {};
Family
and Index
are the “family” and variable index. So for 'w'
and 1
, and for 'x'
and 2
respectively. template<char Family, int Index> struct Node<Variable<Family, Index>> { template<char FPrime, int IPrime> using Derivative_t = std::conditional_t<FPrime == Family && IPrime == Index, Node<Number<1>>, Node<Number<0>>>; static std::string Print () { return std::string { Family, '_' } + std::to_string (Index); } template<typename Vec> static typename Vec::value_type Eval (const Vec& values) { return values (Node {}); } constexpr Node () {} };
FPrime
and IPrime
for the type Derivative_t
are the family and the index of the variable on which you want to take the derivative.values
dictionary, which is passed to the Eval()
function. The dictionary itself is able to find the value of the desired variable by its type, so we will simply give it the type of our variable and return the corresponding value. As a dictionary it does, we will look at it later. enum class UnaryFunction { Sin, Cos, Ln, Neg }; template<UnaryFunction UF> struct UnaryFunctionWrapper;
UnaryFunctionWrapper
specialization UnaryFunctionWrapper
we stuff the logic by taking the derivatives of each specific unary function. To minimize duplicate code, we will take the derivative of the unary function by its argument, the caller will answer for further differentiation of the argument by the target variable through the chain rule: template<> struct UnaryFunctionWrapper<UnaryFunction::Sin> { template<typename Child> using Derivative_t = Node<Cos, Child>; }; template<> struct UnaryFunctionWrapper<UnaryFunction::Cos> { template<typename Child> using Derivative_t = Node<Neg, Node<Sin, Child>>; }; template<> struct UnaryFunctionWrapper<UnaryFunction::Ln> { template<typename Child> using Derivative_t = Node<Div, Node<Number<1>>, Child>; }; template<> struct UnaryFunctionWrapper<UnaryFunction::Neg> { template<typename> using Derivative_t = Node<Number<-1>>; };
template<UnaryFunction UF, typename... ChildArgs> struct Node<UnaryFunctionWrapper<UF>, Node<ChildArgs...>> { using Child_t = Node<ChildArgs...>; template<char FPrime, int IPrime> using Derivative_t = Node<Mul, typename UnaryFunctionWrapper<UF>::template Derivative_t<Child_t>, typename Node<ChildArgs...>::template Derivative_t<FPrime, IPrime>>; static std::string Print () { return FunctionName (UF) + "(" + Node<ChildArgs...>::Print () + ")"; } template<typename Vec> static typename Vec::value_type Eval (const Vec& values) { const auto child = Child_t::Eval (values); return EvalUnary (UnaryFunctionWrapper<UF> {}, child); } };
EvalUnary()
function. Rather, a family of functions: the first argument of the function is the type that defines our unary function, to ensure the choice of the desired overload during compilation. Yes, one could pass the UF
value itself, and a smart compiler would almost certainly make all the necessary constant propagation passes, but here it’s easier to be safe. template<> struct BinaryFunctionWrapper<BinaryFunction::Div> { template<char Family, int Index, typename U, typename V> using Derivative_t = Node<Div, Node<Add, Node<Mul, typename U::template Derivative_t<Family, Index>, V >, Node<Neg, Node<Mul, U, typename V::template Derivative_t<Family, Index> > > >, Node<Mul, V, V > >; };
VarDerivative_t
is determined quite simply, because in fact it only calls Derivative_t
on the node passed to it: template<typename Node, typename Var> struct VarDerivative; template<typename Expr, char Family, int Index> struct VarDerivative<Expr, Node<Variable<Family, Index>>> { using Result_t = typename Expr::template Derivative_t<Family, Index>; }; template<typename Node, typename Var> using VarDerivative_t = typename VarDerivative<Node, std::decay_t<Var>>::Result_t;
// : using Sin = UnaryFunctionWrapper<UnaryFunction::Sin>; using Cos = UnaryFunctionWrapper<UnaryFunction::Cos>; using Neg = UnaryFunctionWrapper<UnaryFunction::Neg>; using Ln = UnaryFunctionWrapper<UnaryFunction::Ln>; using Add = BinaryFunctionWrapper<BinaryFunction::Add>; using Mul = BinaryFunctionWrapper<BinaryFunction::Mul>; using Div = BinaryFunctionWrapper<BinaryFunction::Div>; using Pow = BinaryFunctionWrapper<BinaryFunction::Pow>; // variable template C++14 : template<char Family, int Index = 0> constexpr Node<Variable<Family, Index>> Var {}; // x0 , , : using X0 = Node<Variable<'x', 0>>; constexpr X0 x0; // // , : constexpr Node<Number<1>> _1; // , , : template<typename T1, typename T2> Node<Add, std::decay_t<T1>, std::decay_t<T2>> operator+ (T1, T2); template<typename T1, typename T2> Node<Mul, std::decay_t<T1>, std::decay_t<T2>> operator* (T1, T2); template<typename T1, typename T2> Node<Div, std::decay_t<T1>, std::decay_t<T2>> operator/ (T1, T2); template<typename T1, typename T2> Node<Add, std::decay_t<T1>, Node<Neg, std::decay_t<T2>>> operator- (T1, T2); // , : template<typename T> Node<Sin, std::decay_t<T>> Sin (T); template<typename T> Node<Cos, std::decay_t<T>> Cos (T); template<typename T> Node<Ln, std::decay_t<T>> Ln (T);
Eval()
function. Secondly, to mention the possibility of converting the desired expression with the replacement of one subtree with another. Let's start with the second, it's easier.ApplyDependency_t
metafunction is used for this, although it would be Rewrite_t
correct to call it Rewrite_t
or something like that), differentiate, return using Unwrapped_t = ApplyDependency_t<decltype (logr0), decltype (Ln (r0)), Formula_t>; using Derivative_t = VarDerivative_t<Unwrapped_t, decltype (r0)>; using CacheLog_t = ApplyDependency_t<decltype (Ln (r0)), decltype (logr0), Derivative_t>;
template<typename Var, typename Expr, typename Formula, typename Enable = void> struct ApplyDependency { using Result_t = Formula; }; template<typename Var, typename Expr, typename Formula> using ApplyDependency_t = typename ApplyDependency<std::decay_t<Var>, std::decay_t<Expr>, Formula>::Result_t; template<typename Var, typename Expr, UnaryFunction UF, typename Child> struct ApplyDependency<Var, Expr, Node<UnaryFunctionWrapper<UF>, Child>, std::enable_if_t<!std::is_same<Var, Node<UnaryFunctionWrapper<UF>, Child>>::value>> { using Result_t = Node< UnaryFunctionWrapper<UF>, ApplyDependency_t<Var, Expr, Child> >; }; template<typename Var, typename Expr, BinaryFunction BF, typename FirstNode, typename SecondNode> struct ApplyDependency<Var, Expr, Node<BinaryFunctionWrapper<BF>, FirstNode, SecondNode>, std::enable_if_t<!std::is_same<Var, Node<BinaryFunctionWrapper<BF>, FirstNode, SecondNode>>::value>> { using Result_t = Node< BinaryFunctionWrapper<BF>, ApplyDependency_t<Var, Expr, FirstNode>, ApplyDependency_t<Var, Expr, SecondNode> >; }; template<typename Var, typename Expr> struct ApplyDependency<Var, Expr, Var> { using Result_t = Expr; };
auto GetValue (Variable<'x', 0>) { return value_for_x0; } auto GetValue (Variable<'x', 1>) { return value_for_x1; } ...
BuildFunctor (g0, someValue, alpha0, anotherValue, k, yetOneMoreValue, r0, independentVariable, logr0, logOfTheIndependentVariable);
g0
, alpha0
and company are objects that have the types of the corresponding variables, followed by the corresponding values. template<typename ValueType, typename NodeType> auto BuildFunctor (NodeType, ValueType val) { return [val] (NodeType) { return val; }; }
template<typename F, typename S> struct Map : F, S { using F::operator(); using S::operator(); Map (F f, S s) : F { std::forward<F> (f) } , S { std::forward<S> (s) } { } };
Map
from the first two lambdas, the next Map
from the first Map
and the next lambda, and so on. Let's make it in the form of a code: template<typename F> auto Augment (F&& f) { return f; } template<typename F, typename S> auto Augment (F&& f, S&& s) { return Map<std::decay_t<F>, std::decay_t<S>> { f, s }; } template<typename ValueType> auto BuildFunctor () { struct { ValueType operator() () const { return {}; } using value_type = ValueType; } dummy; return dummy; } template<typename ValueType, typename NodeType, typename... Tail> auto BuildFunctor (NodeType, ValueType val, Tail&&... tail) { return detail::Augment ([val] (NodeType) { return val; }, BuildFunctor<ValueType> (std::forward<Tail> (tail)...)); }
Source: https://habr.com/ru/post/310016/
All Articles