// Copyright (C) 2016-2018 T. Zachary Laine // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "autodiff.h" #include #include #include #include #define BOOST_TEST_MODULE autodiff_test #include double const Epsilon = 10.0e-6; #define CHECK_CLOSE(A,B) do { BOOST_CHECK_CLOSE(A,B,Epsilon); } while(0) using namespace AutoDiff; //[ autodiff_expr_template_decl template struct autodiff_expr { static boost::yap::expr_kind const kind = Kind; Tuple elements; }; BOOST_YAP_USER_UNARY_OPERATOR(negate, autodiff_expr, autodiff_expr) BOOST_YAP_USER_BINARY_OPERATOR(plus, autodiff_expr, autodiff_expr) BOOST_YAP_USER_BINARY_OPERATOR(minus, autodiff_expr, autodiff_expr) BOOST_YAP_USER_BINARY_OPERATOR(multiplies, autodiff_expr, autodiff_expr) BOOST_YAP_USER_BINARY_OPERATOR(divides, autodiff_expr, autodiff_expr) //] //[ autodiff_expr_literals_decl namespace autodiff_placeholders { // This defines a placeholder literal operator that creates autodiff_expr // placeholders. BOOST_YAP_USER_LITERAL_PLACEHOLDER_OPERATOR(autodiff_expr) } //] //[ autodiff_function_terminals template struct autodiff_fn_expr : autodiff_expr> { autodiff_fn_expr () : autodiff_expr {boost::hana::tuple{Opcode}} {} BOOST_YAP_USER_CALL_OPERATOR_N(::autodiff_expr, 1); }; // Someone included , so we have to add trailing underscores. autodiff_fn_expr const sin_; autodiff_fn_expr const cos_; autodiff_fn_expr const sqrt_; //] //[ autodiff_xform struct xform { // Create a var-node for each placeholder when we see it for the first // time. template Node * operator() (boost::yap::expr_tag, boost::yap::placeholder) { if (list_.size() < I) list_.resize(I); auto & retval = list_[I - 1]; if (retval == nullptr) retval = create_var_node(); return retval; } // Create a param-node for every numeric terminal in the expression. Node * operator() (boost::yap::expr_tag, double x) { return create_param_node(x); } // Create a "uary" node for each call expression, using its OPCODE. template Node * operator() (boost::yap::expr_tag, OPCODE opcode, Expr const & expr) { return create_uary_op_node( opcode, boost::yap::transform(boost::yap::as_expr(expr), *this) ); } template Node * operator() (boost::yap::expr_tag, Expr const & expr) { return create_uary_op_node( OP_NEG, boost::yap::transform(boost::yap::as_expr(expr), *this) ); } // Define a mapping from binary arithmetic expr_kind to OPCODE... static OPCODE op_for_kind (boost::yap::expr_kind kind) { switch (kind) { case boost::yap::expr_kind::plus: return OP_PLUS; case boost::yap::expr_kind::minus: return OP_MINUS; case boost::yap::expr_kind::multiplies: return OP_TIMES; case boost::yap::expr_kind::divides: return OP_DIVID; default: assert(!"This should never execute"); return OPCODE{}; } assert(!"This should never execute"); return OPCODE{}; } // ... and use it to handle all the binary arithmetic operators. template Node * operator() (boost::yap::expr_tag, Expr1 const & expr1, Expr2 const & expr2) { return create_binary_op_node( op_for_kind(Kind), boost::yap::transform(boost::yap::as_expr(expr1), *this), boost::yap::transform(boost::yap::as_expr(expr2), *this) ); } vector & list_; }; //] //[ autodiff_to_node template Node * to_auto_diff_node (Expr const & expr, vector & list, T ... args) { Node * retval = nullptr; // This fills in list as a side effect. retval = boost::yap::transform(expr, xform{list}); assert(list.size() == sizeof...(args)); // Fill in the values of the value-nodes in list with the "args" // parameter pack. auto it = list.begin(); boost::hana::for_each( boost::hana::make_tuple(args ...), [&it](auto x) { Node * n = *it; VNode * v = boost::polymorphic_downcast(n); v->val = x; ++it; } ); return retval; } //] struct F{ F() { AutoDiff::autodiff_setup(); } ~F(){ AutoDiff::autodiff_cleanup(); } }; BOOST_FIXTURE_TEST_SUITE(all, F) //[ autodiff_original_node_builder Node* build_linear_fun1_manually(vector& list) { //f(x1,x2,x3) = -5*x1+sin(10)*x1+10*x2-x3/6 PNode* v5 = create_param_node(-5); PNode* v10 = create_param_node(10); PNode* v6 = create_param_node(6); VNode* x1 = create_var_node(); VNode* x2 = create_var_node(); VNode* x3 = create_var_node(); OPNode* op1 = create_binary_op_node(OP_TIMES,v5,x1); //op1 = v5*x1 OPNode* op2 = create_uary_op_node(OP_SIN,v10); //op2 = sin(v10) OPNode* op3 = create_binary_op_node(OP_TIMES,op2,x1); //op3 = op2*x1 OPNode* op4 = create_binary_op_node(OP_PLUS,op1,op3); //op4 = op1 + op3 OPNode* op5 = create_binary_op_node(OP_TIMES,v10,x2); //op5 = v10*x2 OPNode* op6 = create_binary_op_node(OP_PLUS,op4,op5); //op6 = op4+op5 OPNode* op7 = create_binary_op_node(OP_DIVID,x3,v6); //op7 = x3/v6 OPNode* op8 = create_binary_op_node(OP_MINUS,op6,op7); //op8 = op6 - op7 x1->val = -1.9; x2->val = 2; x3->val = 5./6.; list.push_back(x1); list.push_back(x2); list.push_back(x3); return op8; } //] //[ autodiff_yap_node_builder Node* build_linear_fun1(vector& list) { //f(x1,x2,x3) = -5*x1+sin(10)*x1+10*x2-x3/6 using namespace autodiff_placeholders; return to_auto_diff_node( -5 * 1_p + sin_(10) * 1_p + 10 * 2_p - 3_p / 6, list, -1.9, 2, 5./6. ); } //] Node* build_linear_function2_manually(vector& list) { //f(x1,x2,x3) = -5*x1+-10*x1+10*x2-x3/6 PNode* v5 = create_param_node(-5); PNode* v10 = create_param_node(10); PNode* v6 = create_param_node(6); VNode* x1 = create_var_node(); VNode* x2 = create_var_node(); VNode* x3 = create_var_node(); list.push_back(x1); list.push_back(x2); list.push_back(x3); OPNode* op1 = create_binary_op_node(OP_TIMES,v5,x1); //op1 = v5*x1 OPNode* op2 = create_uary_op_node(OP_NEG,v10); //op2 = -v10 OPNode* op3 = create_binary_op_node(OP_TIMES,op2,x1);//op3 = op2*x1 OPNode* op4 = create_binary_op_node(OP_PLUS,op1,op3);//op4 = op1 + op3 OPNode* op5 = create_binary_op_node(OP_TIMES,v10,x2);//op5 = v10*x2 OPNode* op6 = create_binary_op_node(OP_PLUS,op4,op5);//op6 = op4+op5 OPNode* op7 = create_binary_op_node(OP_DIVID,x3,v6); //op7 = x3/v6 OPNode* op8 = create_binary_op_node(OP_MINUS,op6,op7);//op8 = op6 - op7 x1->val = -1.9; x2->val = 2; x3->val = 5./6.; return op8; } Node* build_linear_function2(vector& list) { //f(x1,x2,x3) = -5*x1+-10*x1+10*x2-x3/6 using namespace autodiff_placeholders; auto ten = boost::yap::make_terminal(10); return to_auto_diff_node( -5 * 1_p + -ten * 1_p + 10 * 2_p - 3_p / 6, list, -1.9, 2, 5./6. ); } Node* build_nl_function1_manually(vector& list) { // (x1*x2 * sin(x1))/x3 + x2*x4 - x1/x2 VNode* x1 = create_var_node(); VNode* x2 = create_var_node(); VNode* x3 = create_var_node(); VNode* x4 = create_var_node(); x1->val = -1.23; x2->val = 7.1231; x3->val = 2; x4->val = -10; list.push_back(x1); list.push_back(x2); list.push_back(x3); list.push_back(x4); OPNode* op1 = create_binary_op_node(OP_TIMES,x2,x1); OPNode* op2 = create_uary_op_node(OP_SIN,x1); OPNode* op3 = create_binary_op_node(OP_TIMES,op1,op2); OPNode* op4 = create_binary_op_node(OP_DIVID,op3,x3); OPNode* op5 = create_binary_op_node(OP_TIMES,x2,x4); OPNode* op6 = create_binary_op_node(OP_PLUS,op4,op5); OPNode* op7 = create_binary_op_node(OP_DIVID,x1,x2); OPNode* op8 = create_binary_op_node(OP_MINUS,op6,op7); return op8; } Node* build_nl_function1(vector& list) { // (x1*x2 * sin(x1))/x3 + x2*x4 - x1/x2 using namespace autodiff_placeholders; return to_auto_diff_node( (1_p * 2_p * sin_(1_p)) / 3_p + 2_p * 4_p - 1_p / 2_p, list, -1.23, 7.1231, 2, -10 ); } BOOST_AUTO_TEST_CASE( test_linear_fun1 ) { BOOST_TEST_MESSAGE("test_linear_fun1"); vector list; Node* root = build_linear_fun1(list); vector grad; double val1 = grad_reverse(root,list,grad); double val2 = eval_function(root); double x1g[] = {-5.5440211108893697744548489936278,10.0,-0.16666666666666666666666666666667}; for(unsigned int i=0;i<3;i++){ CHECK_CLOSE(grad[i],x1g[i]); } double eval = 30.394751221800913; CHECK_CLOSE(val1,eval); CHECK_CLOSE(val2,eval); EdgeSet s; nonlinearEdges(root,s); unsigned int n = nzHess(s); BOOST_CHECK_EQUAL(n,0); } BOOST_AUTO_TEST_CASE( test_grad_sin ) { BOOST_TEST_MESSAGE("test_grad_sin"); VNode* x1 = create_var_node(); x1->val = 10; OPNode* root = create_uary_op_node(OP_SIN,x1); vector nodes; nodes.push_back(x1); vector grad; grad_reverse(root,nodes,grad); double x1g = -0.83907152907645244; //the matlab give cos(10) = -0.839071529076452 CHECK_CLOSE(grad[0],x1g); BOOST_CHECK_EQUAL(nodes.size(),1); EdgeSet s; nonlinearEdges(root,s); unsigned int n = nzHess(s); BOOST_CHECK_EQUAL(n,1); } BOOST_AUTO_TEST_CASE(test_grad_single_node) { VNode* x1 = create_var_node(); x1->val = -2; vector nodes; nodes.push_back(x1); vector grad; double val = grad_reverse(x1,nodes,grad); CHECK_CLOSE(grad[0],1); CHECK_CLOSE(val,-2); EdgeSet s; unsigned int n = 0; nonlinearEdges(x1,s); n = nzHess(s); BOOST_CHECK_EQUAL(n,0); grad.clear(); nodes.clear(); PNode* p = create_param_node(-10); //OPNode* op = create_binary_op_node(TIMES,p,create_param_node(2)); val = grad_reverse(p,nodes,grad); BOOST_CHECK_EQUAL(grad.size(),0); CHECK_CLOSE(val,-10); s.clear(); nonlinearEdges(p,s); n = nzHess(s); BOOST_CHECK_EQUAL(n,0); } BOOST_AUTO_TEST_CASE(test_grad_neg) { VNode* x1 = create_var_node(); x1->val = 10; PNode* p2 = create_param_node(-1); vector nodes; vector grad; nodes.push_back(x1); Node* root = create_binary_op_node(OP_TIMES,x1,p2); grad_reverse(root,nodes,grad); CHECK_CLOSE(grad[0],-1); BOOST_CHECK_EQUAL(nodes.size(),1); nodes.clear(); grad.clear(); nodes.push_back(x1); root = create_uary_op_node(OP_NEG,x1); grad_reverse(root,nodes,grad); CHECK_CLOSE(grad[0],-1); EdgeSet s; unsigned int n = 0; nonlinearEdges(root,s); n = nzHess(s); BOOST_CHECK_EQUAL(n,0); } BOOST_AUTO_TEST_CASE( test_nl_function) { vector list; Node* root = build_nl_function1(list); double val = eval_function(root); vector grad; grad_reverse(root,list,grad); double eval =-66.929555552886214; double gx[] = {-4.961306690356109,-9.444611307649055,-2.064383410399700,7.123100000000000}; CHECK_CLOSE(val,eval); for(unsigned int i=0;i<4;i++) { CHECK_CLOSE(grad[i],gx[i]); } unsigned int nzgrad = nzGrad(root); unsigned int tol = numTotalNodes(root); BOOST_CHECK_EQUAL(nzgrad,4); BOOST_CHECK_EQUAL(tol,16); EdgeSet s; nonlinearEdges(root,s); unsigned int n = nzHess(s); BOOST_CHECK_EQUAL(n,11); } BOOST_AUTO_TEST_CASE( test_hess_reverse_1) { vector nodes; Node* root = build_linear_fun1(nodes); vector grad; double val = grad_reverse(root,nodes,grad); double eval = eval_function(root); // cout<(nodes[i])->u = 0; } static_cast(nodes[0])->u = 1; double hval = 0; vector dhess; hval = hess_reverse(root,nodes,dhess); CHECK_CLOSE(hval,eval); for(unsigned int i=0;i nodes; Node* root = build_linear_function2(nodes); vector grad; double val = grad_reverse(root,nodes,grad); double eval = eval_function(root); CHECK_CLOSE(val,eval); for(unsigned int i=0;i(nodes[i])->u = 0; } static_cast(nodes[0])->u = 1; double hval = 0; vector dhess; hval = hess_reverse(root,nodes,dhess); CHECK_CLOSE(hval,eval); for(unsigned int i=0;i nodes; // Node* root = build_nl_function1(nodes); VNode* x1 = create_var_node(); nodes.push_back(x1); x1->val = 1; x1->u =1; Node* op = create_uary_op_node(OP_SIN,x1); Node* root = create_uary_op_node(OP_SIN,op); vector grad; double eval = eval_function(root); vector dhess; double hval = hess_reverse(root,nodes,dhess); CHECK_CLOSE(hval,eval); BOOST_CHECK_EQUAL(dhess.size(),1); CHECK_CLOSE(dhess[0], -0.778395788418109); EdgeSet s; nonlinearEdges(root,s); unsigned int n = nzHess(s); BOOST_CHECK_EQUAL(n,1); } BOOST_AUTO_TEST_CASE( test_hess_reverse_3) { vector nodes; VNode* x1 = create_var_node(); VNode* x2 = create_var_node(); nodes.push_back(x1); nodes.push_back(x2); x1->val = 2.5; x2->val = -9; Node* op1 = create_binary_op_node(OP_TIMES,x1,x2); Node* root = create_binary_op_node(OP_TIMES,x1,op1); double eval = eval_function(root); for(unsigned int i=0;i(nodes[i])->u = 0; } static_cast(nodes[0])->u = 1; vector dhess; double hval = hess_reverse(root,nodes,dhess); BOOST_CHECK_EQUAL(dhess.size(),2); CHECK_CLOSE(hval,eval); double hx[]={-18,5}; for(unsigned int i=0;i