J'essaie de modifier l'exemple ici :
# include <cppad/cppad.hpp>
namespace { // ---------------------------------------------------------
// define the template function JacobianCases<Vector> in empty namespace
template <typename Vector>
bool JacobianCases()
{ bool ok = true;
using CppAD::AD;
using CppAD::NearEqual;
double eps99 = 99.0 * std::numeric_limits<double>::epsilon();
using CppAD::exp;
using CppAD::sin;
using CppAD::cos;
// domain space vector
size_t n = 2;
CPPAD_TESTVECTOR(AD<double>) X(n);
X[0] = 1.;
X[1] = 2.;
// declare independent variables and starting recording
CppAD::Independent(X);
// a calculation between the domain and range values
AD<double> Square = X[0] * X[0];
// range space vector
size_t m = 3;
CPPAD_TESTVECTOR(AD<double>) Y(m);
Y[0] = Square * exp( X[1] );
Y[1] = Square * sin( X[1] );
Y[2] = Square * cos( X[1] );
// create f: X -> Y and stop tape recording
CppAD::ADFun<double> f(X, Y);
// new value for the independent variable vector
Vector x(n);
x[0] = 2.;
x[1] = 1.;
// compute the derivative at this x
Vector jac( m * n );
jac = f.Jacobian(x);
/*
F'(x) = [ 2 * x[0] * exp(x[1]) , x[0] * x[0] * exp(x[1]) ]
[ 2 * x[0] * sin(x[1]) , x[0] * x[0] * cos(x[1]) ]
[ 2 * x[0] * cos(x[1]) , -x[0] * x[0] * sin(x[i]) ]
*/
ok &= NearEqual( 2.*x[0]*exp(x[1]), jac[0*n+0], eps99, eps99);
ok &= NearEqual( 2.*x[0]*sin(x[1]), jac[1*n+0], eps99, eps99);
ok &= NearEqual( 2.*x[0]*cos(x[1]), jac[2*n+0], eps99, eps99);
ok &= NearEqual( x[0] * x[0] *exp(x[1]), jac[0*n+1], eps99, eps99);
ok &= NearEqual( x[0] * x[0] *cos(x[1]), jac[1*n+1], eps99, eps99);
ok &= NearEqual(-x[0] * x[0] *sin(x[1]), jac[2*n+1], eps99, eps99);
return ok;
}
} // End empty namespace
# include <vector>
# include <valarray>
bool Jacobian(void)
{ bool ok = true;
// Run with Vector equal to three different cases
// all of which are Simple Vectors with elements of type double.
ok &= JacobianCases< CppAD::vector <double> >();
ok &= JacobianCases< std::vector <double> >();
ok &= JacobianCases< std::valarray <double> >();
return ok;
}
J'essaie de le modifier de la manière suivante:
Soit G le Jacobien jac
calculé dans cet exemple, dans la ligne:
jac = f.Jacobian(x);
et, comme dans l'exemple, laissez X
les variables indépendantes. Je voudrais construire une nouvelle fonction, H
, qui est une fonction de jac
, c'est-à-dire H(jacobian(X))
= quelque chose, tel que H est autodifférentiable. Un exemple peut être H(X) = jacobian( jacobian(X)[0])
, c'est-à-dire le jacobien du premier élément de jacobian(X)
w.r.t X
(une dérivée seconde de la sorte).
Le problème est que jac
, tel qu'écrit ici, est du type Vector
, qui est un type paramétré sur un double
brut, et non un AD<double>
. À ma connaissance, cela signifie que la sortie n'est pas autodifférenciable.
Je cherche des conseils pour savoir s'il est possible d'utiliser le jacobien dans une opération plus grande et de prendre le jacobien de cette opération plus importante (pas n'importe quel opérateur arithmétique) ou si cela n'est pas possible.
EDIT: Cela a déjà été offert pour une prime, mais je le répète pour voir s'il existe une meilleure solution, car je pense que c'est important. Pour être un peu plus clair, les éléments dont la réponse "correcte" a besoin sont:
a) Un moyen de calculer les dérivées d'ordre arbitraire.
b) Une manière intelligente de ne pas avoir à spécifier l'ordre des dérivés à priori. Si la dérivée d'ordre maximum doit être connue au moment de la compilation, l'ordre de la dérivée ne peut pas être déterminé par un algorithme. De plus, spécifier un ordre extrêmement volumineux comme dans la réponse actuelle donnera lieu à des problèmes d'allocation de mémoire et, j'imagine, à des problèmes de performances.
c) Abstraction de la modélisation de l'ordre dérivé à l'utilisateur final. Ceci est important car il peut être difficile de suivre l’ordre des produits dérivés nécessaires. C'est probablement quelque chose qui vient "gratuitement" si b) est résolu.
Si quelqu'un peut résoudre ce problème, ce sera une contribution formidable et une opération extrêmement utile.
Si vous souhaitez imbriquer des fonctions, vous devez également imbriquer AD <>. Vous pouvez imbriquer des jacobiens comme d'autres fonctions, par exemple voir l'extrait de code ci-dessous, qui calcule la dérivée double en imbriquant jacobian.
#include <cstring>
#include <iostream> // standard input/output
#include <vector> // standard vector
#include <cppad/cppad.hpp> // the CppAD package http://www.coin-or.org/CppAD/
// main program
int main(void)
{ using CppAD::AD; // use AD as abbreviation for CppAD::AD
using std::vector; // use vector as abbreviation for std::vector
size_t i; // a temporary index
// domain space vector
auto Square = [](auto t){return t*t;};
vector< AD<AD<double>> > X(1); // vector of domain space variables
// declare independent variables and start recording operation sequence
CppAD::Independent(X);
// range space vector
vector< AD<AD<double>> > Y(1); // vector of ranges space variables
Y[0] = Square(X[0]); // value during recording of operations
// store operation sequence in f: X -> Y and stop recording
CppAD::ADFun<AD<double>> f(X, Y);
// compute derivative using operation sequence stored in f
vector<AD<double>> jac(1); // Jacobian of f (m by n matrix)
vector<AD<double>> x(1); // domain space vector
CppAD::Independent(x);
jac = f.Jacobian(x); // Jacobian for operation sequence
CppAD::ADFun<double> f2(x, jac);
vector<double> result(1);
vector<double> x_res(1);
x_res[0]=15.;
result=f2.Jacobian(x_res);
// print the results
std::cout << "f'' computed by CppAD = " << result[0] << std::endl;
}
En remarque, depuis que C++ 14 ou 11, implémenter des modèles d’expression et une différenciation automatique est devenu plus facile et peut être fait avec beaucoup moins d’effort, comme indiqué par ex. dans cette vidéo vers la fin https://www.youtube.com/watch?v=cC9MtflQ_nI (désolé pour la mauvaise qualité). Si je devais mettre en oeuvre des opérations symboliques relativement simples, je commencerais à zéro avec le C++ moderne: vous pouvez écrire du code plus simple et vous obtenez des erreurs que vous pouvez comprendre facilement.
Edit: Généraliser l'exemple pour construire des dérivées d'ordre arbitraire peut être un exercice de métaprogrammation de gabarit. L'extrait ci-dessous montre qu'il est possible d'utiliser la récursion de modèles
#include <cstring>
#include <iostream>
#include <vector>
#include <cppad/cppad.hpp>
using CppAD::AD;
using std::vector;
template<typename T>
struct remove_ad{
using type=T;
};
template<typename T>
struct remove_ad<AD<T>>{
using type=T;
};
template<int N>
struct derivative{
using type = AD<typename derivative<N-1>::type >;
static constexpr int order = N;
};
template<>
struct derivative<0>{
using type = double;
static constexpr int order = 0;
};
template<typename T>
struct Jac{
using value_type = typename remove_ad<typename T::type>::type;
template<typename P, typename Q>
auto operator()(P & X, Q & Y){
CppAD::ADFun<value_type> f(X, Y);
vector<value_type> jac(1);
vector<value_type> x(1);
CppAD::Independent(x);
jac = f.Jacobian(x);
return Jac<derivative<T::order-1>>{}(x, jac);
}
};
template<>
struct Jac<derivative<1>>{
using value_type = derivative<0>::type;
template<typename P, typename Q>
auto operator()(P & x, Q & jac){
CppAD::ADFun<value_type> f2(x, jac);
vector<value_type> res(1);
vector<value_type> x_res(1);
x_res[0]=15.;
return f2.Jacobian(x_res);
}
};
int main(void)
{
constexpr int order=4;
auto Square = [](auto t){return t*t;};
vector< typename derivative<order>::type > X(1);
vector< typename derivative<order>::type > Y(1);
CppAD::Independent(X);
Y[0] = Square(X[0]);
auto result = Jac<derivative<order>>{}(X, Y);
std::cout << "f'' computed by CppAD = " << result[0] << std::endl;
}
Il existe une nouvelle fonctionnalité dans CppAD qui élimine le besoin de AD <AD>, voir https://coin-or.github.io/CppAD/doc/base2ad.cpp.htm