diff --git a/Jenkinsfile b/Jenkinsfile index 07d547b598c..04825b792ff 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -248,31 +248,31 @@ pipeline { } post { always { retry(3) { deleteDir() } } } } - stage('OpenCL tests async') { - agent { label "gpu-async" } - when { - expression { - runGpuAsync - } - } - steps { - deleteDir() - unstash 'MathSetup' - sh "echo CXX=${env.CXX} -Werror > make/local" - sh "echo STAN_OPENCL=true>> make/local" - sh "echo OPENCL_PLATFORM_ID=0>> make/local" - sh "echo OPENCL_DEVICE_ID=${OPENCL_DEVICE_ID}>> make/local" - sh "make -j${env.PARALLEL} test-headers" - runTests("test/unit/math/opencl") - runTests("test/unit/math/prim/fun/gp_exp_quad_cov_test") - runTests("test/unit/math/prim/fun/mdivide_left_tri_test") - runTests("test/unit/math/prim/fun/mdivide_right_tri_test") - runTests("test/unit/math/prim/fun/multiply_test") - runTests("test/unit/math/rev/fun/mdivide_left_tri_test") - runTests("test/unit/math/rev/fun/multiply_test") - } - post { always { retry(3) { deleteDir() } } } - } + // stage('OpenCL tests async') { + // agent { label "gpu-async" } + // when { + // expression { + // runGpuAsync + // } + // } + // steps { + // deleteDir() + // unstash 'MathSetup' + // sh "echo CXX=${env.CXX} -Werror > make/local" + // sh "echo STAN_OPENCL=true>> make/local" + // sh "echo OPENCL_PLATFORM_ID=0>> make/local" + // sh "echo OPENCL_DEVICE_ID=${OPENCL_DEVICE_ID}>> make/local" + // sh "make -j${env.PARALLEL} test-headers" + // runTests("test/unit/math/opencl") + // runTests("test/unit/math/prim/fun/gp_exp_quad_cov_test") + // runTests("test/unit/math/prim/fun/mdivide_left_tri_test") + // runTests("test/unit/math/prim/fun/mdivide_right_tri_test") + // runTests("test/unit/math/prim/fun/multiply_test") + // runTests("test/unit/math/rev/fun/mdivide_left_tri_test") + // runTests("test/unit/math/rev/fun/multiply_test") + // } + // post { always { retry(3) { deleteDir() } } } + // } stage('Distribution tests') { agent { label "distribution-tests" } steps { diff --git a/stan/math/prim/err.hpp b/stan/math/prim/err.hpp index 049904e7023..e38f0fc53ee 100644 --- a/stan/math/prim/err.hpp +++ b/stan/math/prim/err.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include diff --git a/stan/math/prim/err/check_sorted.hpp b/stan/math/prim/err/check_sorted.hpp new file mode 100644 index 00000000000..702a4466787 --- /dev/null +++ b/stan/math/prim/err/check_sorted.hpp @@ -0,0 +1,76 @@ +#ifndef STAN_MATH_PRIM_ERR_CHECK_SORTED_HPP +#define STAN_MATH_PRIM_ERR_CHECK_SORTED_HPP + +#include +#include +#include +#include +#include + +namespace stan { +namespace math { + +/** + * Check if the specified vector is sorted into increasing order (repeated + * values are okay). + * @tparam T_y Type of scalar + * @param function Function name (for error messages) + * @param name Variable name (for error messages) + * @param y Vector to test + * @throw std::domain_error if the vector elements are + * not sorted, or if any element is NaN. + */ +template +void check_sorted(const char* function, const char* name, + const Eigen::Matrix& y) { + using size_type = index_type_t>; + + for (size_type n = 1; n < y.size(); n++) { + if (!(y[n] >= y[n - 1])) { + std::ostringstream msg1; + msg1 << "is not a valid sorted vector." + << " The element at " << stan::error_index::value + n << " is "; + std::string msg1_str(msg1.str()); + std::ostringstream msg2; + msg2 << ", but should be greater than or equal to the previous element, " + << y[n - 1]; + std::string msg2_str(msg2.str()); + throw_domain_error(function, name, y[n], msg1_str.c_str(), + msg2_str.c_str()); + } + } +} + +/** + * Check if the specified vector is sorted into increasing order (repeated + * values are okay). + * @tparam T_y Type of scalar + * @param function Function name (for error messages) + * @param name Variable name (for error messages) + * @param y std::vector to test + * @throw std::domain_error if the vector elements are + * not sorted, or if any element + * is NaN. + */ +template +void check_sorted(const char* function, const char* name, + const std::vector& y) { + for (size_t n = 1; n < y.size(); n++) { + if (!(y[n] >= y[n - 1])) { + std::ostringstream msg1; + msg1 << "is not a valid sorted vector." + << " The element at " << stan::error_index::value + n << " is "; + std::string msg1_str(msg1.str()); + std::ostringstream msg2; + msg2 << ", but should be greater than or equal to the previous element, " + << y[n - 1]; + std::string msg2_str(msg2.str()); + throw_domain_error(function, name, y[n], msg1_str.c_str(), + msg2_str.c_str()); + } + } +} + +} // namespace math +} // namespace stan +#endif diff --git a/stan/math/prim/fun.hpp b/stan/math/prim/fun.hpp index 0995013b262..1dcdde8ed05 100644 --- a/stan/math/prim/fun.hpp +++ b/stan/math/prim/fun.hpp @@ -82,6 +82,7 @@ #include #include #include +#include #include #include #include diff --git a/stan/math/prim/fun/eval.hpp b/stan/math/prim/fun/eval.hpp new file mode 100644 index 00000000000..a8d5554b995 --- /dev/null +++ b/stan/math/prim/fun/eval.hpp @@ -0,0 +1,41 @@ +#ifndef STAN_MATH_PRIM_FUN_EVAL_HPP +#define STAN_MATH_PRIM_FUN_EVAL_HPP + +#include +#include + +namespace stan { +namespace math { + +/** + * Inputs which have a plain_type equal to the own time are forwarded + * unmodified (for Eigen expressions these types are different) + * + * @tparam T Input type + * @param[in] arg Input argument + * @return Forwarded input argument + **/ +template , plain_type_t>* = nullptr> +inline decltype(auto) eval(T&& arg) { + return std::forward(arg); +} + +/** + * Inputs which have a plain_type different from their own type are + * Eval'd (this catches Eigen expressions) + * + * @tparam T Input type + * @param[in] arg Input argument + * @return Eval'd argument + **/ +template , plain_type_t>* = nullptr> +inline decltype(auto) eval(const T& arg) { + return arg.eval(); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/prim/fun/value_of.hpp b/stan/math/prim/fun/value_of.hpp index 6354a13d340..8cc47e80025 100644 --- a/stan/math/prim/fun/value_of.hpp +++ b/stan/math/prim/fun/value_of.hpp @@ -10,94 +10,40 @@ namespace stan { namespace math { /** - * Return the value of the specified scalar argument - * converted to a double value. + * Inputs that are arithmetic types or containers of airthmetric types + * are returned from value_of unchanged * - *

See the primitive_value function to - * extract values without casting to double. - * - *

This function is meant to cover the primitive types. For - * types requiring pass-by-reference, this template function - * should be specialized. - * - * @tparam T type of scalar. - * @param x scalar to convert to double - * @return value of scalar cast to double - */ -template * = nullptr> -inline double value_of(const T x) { - return static_cast(x); -} - -/** - * Return the specified argument. - * - *

See value_of(T) for a polymorphic - * implementation using static casts. - * - *

This inline pass-through no-op should be compiled away. - * - * @param x value - * @return input value - */ -template <> -inline double value_of(double x) { - return x; + * @tparam T Input type + * @param[in] x Input argument + * @return Forwarded input argument + **/ +template * = nullptr> +inline decltype(auto) value_of(T&& x) { + return std::forward(x); } /** - * Return the specified argument. - * - *

See value_of(T) for a polymorphic - * implementation using static casts. + * For std::vectors of non-arithmetic types, return a std::vector composed + * of value_of applied to each element. * - *

This inline pass-through no-op should be compiled away. - * - * @param x value - * @return input value - */ -inline int value_of(int x) { return x; } - -/** - * Convert a std::vector of type T to a std::vector of - * child_type::type. - * - * @tparam T Scalar type in std::vector - * @param[in] x std::vector to be converted + * @tparam T Input element type + * @param[in] x Input std::vector * @return std::vector of values **/ -template * = nullptr> -inline std::vector::type> value_of( - const std::vector& x) { - size_t x_size = x.size(); - std::vector::type> result(x_size); - for (size_t i = 0; i < x_size; i++) { - result[i] = value_of(x[i]); +template * = nullptr> +inline auto value_of(const std::vector& x) { + std::vector()))>> out; + out.reserve(x.size()); + for (auto&& x_elem : x) { + out.emplace_back(value_of(x_elem)); } - return result; + return out; } /** - * Return the specified argument. - * - *

See value_of(T) for a polymorphic - * implementation using static casts. - * - *

This inline pass-through no-op should be compiled away. - * - * @param x Specified std::vector. - * @return Specified std::vector. - */ -template * = nullptr> -inline Vec value_of(Vec&& x) { - return std::forward(x); -} - -/** - * Convert a matrix of type T to a matrix of doubles. - * - * T must implement value_of. See - * test/math/fwd/fun/value_of.cpp for fvar and var usage. + * For Eigen matrices and expressions of non-arithmetic types, return an + *expression that represents the Eigen::Matrix resulting from applying value_of + *elementwise * * @tparam EigMat type of the matrix * @@ -105,7 +51,7 @@ inline Vec value_of(Vec&& x) { * @return Matrix of values **/ template * = nullptr, - require_not_vt_double_or_int* = nullptr> + require_not_st_arithmetic* = nullptr> inline auto value_of(EigMat&& M) { return make_holder( [](auto& a) { @@ -114,25 +60,6 @@ inline auto value_of(EigMat&& M) { std::forward(M)); } -/** - * Return the specified argument. - * - *

See value_of(T) for a polymorphic - * implementation using static casts. - * - *

This inline pass-through no-op should be compiled away. - * - * @tparam EigMat type of the matrix - * - * @param x Specified matrix. - * @return Specified matrix. - */ -template * = nullptr> -inline EigMat value_of(EigMat&& x) { - return std::forward(x); -} - } // namespace math } // namespace stan diff --git a/stan/math/prim/functor.hpp b/stan/math/prim/functor.hpp index 15f4512dfda..aab4147aa96 100644 --- a/stan/math/prim/functor.hpp +++ b/stan/math/prim/functor.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -14,6 +13,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -24,4 +26,5 @@ #include #include #include + #endif diff --git a/stan/math/prim/functor/apply.hpp b/stan/math/prim/functor/apply.hpp index a4ebe2d0a37..48673e5b98a 100644 --- a/stan/math/prim/functor/apply.hpp +++ b/stan/math/prim/functor/apply.hpp @@ -26,6 +26,7 @@ constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, return f(std::forward(t))>(std::get(t))...); } } // namespace internal + /* * Call the functor f with the tuple of arguments t, like: * diff --git a/stan/math/prim/functor/coupled_ode_observer.hpp b/stan/math/prim/functor/coupled_ode_observer.hpp deleted file mode 100644 index bf47c4e1bfc..00000000000 --- a/stan/math/prim/functor/coupled_ode_observer.hpp +++ /dev/null @@ -1,165 +0,0 @@ -#ifndef STAN_MATH_PRIM_FUNCTOR_COUPLED_ODE_OBSERVER_HPP -#define STAN_MATH_PRIM_FUNCTOR_COUPLED_ODE_OBSERVER_HPP - -#include -#include -#include -#include - -#include - -namespace stan { -namespace math { - -/** - * Observer for the coupled states. Holds a reference to an - * externally defined vector of vectors passed in at construction time - * which holds the final result on the AD stack. Thus, whenever any of - * the inputs is varying, then the output will be varying as well. The - * sensitivities of the initials and the parameters are taken from the - * coupled state in the order as defined by the - * coupled_ode_system. The sensitivities at each time-point is simply - * the ODE RHS evaluated at that time point. - * - * The output of this class is for all time-points in the ts vector - * which does not contain the initial time-point by the convention - * used in stan-math. - * - */ -template -struct coupled_ode_observer { - using return_t = return_type_t; - - using ops_partials_t - = operands_and_partials, std::vector, T_t0, T_ts>; - - const F& f_; - const std::vector& y0_; - const T_t0& t0_; - const std::vector& ts_; - const std::vector& theta_; - const std::vector& x_; - const std::vector& x_int_; - std::ostream* msgs_; - std::vector>& y_; - const std::size_t N_; - const std::size_t M_; - const std::size_t index_offset_theta_; - std::vector initial_dy_dt_; - int next_ts_index_; - - /** - * Construct a coupled ODE observer for the specified coupled - * vector. - * - * @tparam F type of ODE system function. - * @tparam T1 type of scalars for initial values. - * @tparam T2 type of scalars for parameters. - * @tparam T_t0 type of scalar of initial time point. - * @tparam T_ts type of time-points where ODE solution is returned. - * @param[in] f functor for the base ordinary differential equation. - * @param[in] y0 initial state. - * @param[in] theta parameter vector for the ODE. - * @param[in] t0 initial time. - * @param[in] ts times of the desired solutions, in strictly - * increasing order, all greater than the initial time. - * @param[in] x continuous data vector for the ODE. - * @param[in] x_int integer data vector for the ODE. - * @param[out] msgs the print stream for warning messages. - * @param[out] y reference to a vector of vector of the final return - */ - coupled_ode_observer(const F& f, const std::vector& y0, - const std::vector& theta, const T_t0& t0, - const std::vector& ts, - const std::vector& x, - const std::vector& x_int, std::ostream* msgs, - std::vector>& y) - : f_(f), - y0_(y0), - t0_(t0), - ts_(ts), - theta_(theta), - x_(x), - x_int_(x_int), - msgs_(msgs), - y_(y), - N_(y0.size()), - M_(theta.size()), - index_offset_theta_(is_constant_all::value ? 0 : N_ * N_), - next_ts_index_(0) { - if (!is_constant_all::value) { - initial_dy_dt_ - = f(value_of(t0), value_of(y0), value_of(theta_), x_, x_int_, msgs_); - check_size_match("coupled_ode_observer", "dy_dt", initial_dy_dt_.size(), - "states", N_); - } - } - - /** - * Callback function for ODE solvers to record values. The coupled - * state returned from the solver is added directly to the AD tree. - * - * The coupled state follows the convention as defined in the - * coupled_ode_system. In brief, the coupled state consists of {f, - * df/dy0, df/dtheta}. Here df/dy0 and df/dtheta are only present if - * their respective sensitivites have been requested. - * - * @param coupled_state solution at the specified time. - * @param t time of solution. The time must correspond to the - * element ts[next_ts_index_] - */ - void operator()(const std::vector& coupled_state, double t) { - check_less("coupled_ode_observer", "time-state number", next_ts_index_, - ts_.size()); - - std::vector yt; - yt.reserve(N_); - - ops_partials_t ops_partials(y0_, theta_, t0_, ts_[next_ts_index_]); - - std::vector dy_dt; - if (!is_constant_all::value) { - std::vector y_dbl(coupled_state.begin(), - coupled_state.begin() + N_); - dy_dt = f_(value_of(ts_[next_ts_index_]), y_dbl, value_of(theta_), x_, - x_int_, msgs_); - check_size_match("coupled_ode_observer", "dy_dt", dy_dt.size(), "states", - N_); - } - - for (size_t j = 0; j < N_; j++) { - // iterate over parameters for each equation - if (!is_constant_all::value) { - for (std::size_t k = 0; k < N_; k++) { - ops_partials.edge1_.partials_[k] = coupled_state[N_ + N_ * k + j]; - } - } - - if (!is_constant_all::value) { - for (std::size_t k = 0; k < M_; k++) { - ops_partials.edge2_.partials_[k] - = coupled_state[N_ + index_offset_theta_ + N_ * k + j]; - } - } - - if (!is_constant_all::value) { - ops_partials.edge3_.partials_[0] = -initial_dy_dt_[j]; - } - - if (!is_constant_all::value) { - ops_partials.edge4_.partials_[0] = dy_dt[j]; - } - - yt.emplace_back(ops_partials.build(coupled_state[j])); - } - - y_.emplace_back(yt); - next_ts_index_++; - } -}; - -} // namespace math - -} // namespace stan - -#endif diff --git a/stan/math/prim/functor/coupled_ode_system.hpp b/stan/math/prim/functor/coupled_ode_system.hpp index 3d011fcc998..a4dbe69603d 100644 --- a/stan/math/prim/functor/coupled_ode_system.hpp +++ b/stan/math/prim/functor/coupled_ode_system.hpp @@ -1,103 +1,80 @@ #ifndef STAN_MATH_PRIM_FUNCTOR_COUPLED_ODE_SYSTEM_HPP #define STAN_MATH_PRIM_FUNCTOR_COUPLED_ODE_SYSTEM_HPP +#include #include #include +#include #include #include namespace stan { namespace math { -/** - * The coupled_ode_system represents the coupled ode - * system, which is the base ode and the sensitivities of the base ode - * (derivatives with respect to the parameters of the base ode). - * - * This class provides a functor to be used by ode solvers, a class method - * for the size of the coupled ode system, a class method to retrieve the - * initial state, and a class method to convert from the coupled ode system - * back to the base ode. - * - * @tparam F base ode system functor. Must provide - * operator()(double t, std::vector y, std::vector theta, - * std::vector x, std::vectorx_int, std::ostream* - * msgs) - * @tparam T1 scalar type of the initial state - * @tparam T2 scalar type of the parameters - */ -template -struct coupled_ode_system {}; +template +struct coupled_ode_system_impl; /** - * coupled_ode_system specialization for for known - * initial values and known parameters. - * - * For this case, the coupled ode is the same as the base ode. There - * are no sensitivity parameters and the size of the coupled ode - * system is the size of the base ode system. + * The coupled_ode_system_impl for arithmetic arguments reduces to + * the regular ode system (there are no sensitivities) * * @tparam F base ode system functor. Must provide - * operator()(double t, std::vector y, - * std::vector theta, std::vector x, - * std::vectorx_int, std::ostream* msgs) + * + * template + * operator()(double t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_args&... args) */ -template -class coupled_ode_system { - public: +template +struct coupled_ode_system_impl { const F& f_; - const std::vector& y0_dbl_; - const std::vector& theta_dbl_; - const std::vector& x_; - const std::vector& x_int_; + const Eigen::VectorXd& y0_; + std::tuple args_tuple_; const size_t N_; - const size_t M_; - const size_t size_; std::ostream* msgs_; /** - * Construct the coupled ode system from the base system function, - * initial state of the base system, parameters, data and a stream - * for messages. + * Construct a coupled ode system from the base system function, + * initial state of the base system, parameters, and a stream for + * messages. * - * @param[in] f base ode system functor - * @param[in] y0 initial state of the base ode - * @param[in] theta parameters of the base ode - * @param[in] x real data - * @param[in] x_int integer data + * @param[in] f the base ODE system functor + * @param[in] y0 the initial state of the base ode * @param[in, out] msgs stream for messages + * @param[in] args other additional arguments */ - coupled_ode_system(const F& f, const std::vector& y0, - const std::vector& theta, - const std::vector& x, - const std::vector& x_int, std::ostream* msgs) - : f_(f), - y0_dbl_(y0), - theta_dbl_(theta), - x_(x), - x_int_(x_int), - N_(y0.size()), - M_(theta.size()), - size_(N_), - msgs_(msgs) {} + coupled_ode_system_impl(const F& f, const Eigen::VectorXd& y0, + std::ostream* msgs, const Args&... args) + : f_(f), y0_(y0), args_tuple_(args...), N_(y0.size()), msgs_(msgs) {} /** - * Calculates the derivative of the coupled ode system with respect to time. + * Evaluate the right hand side of the coupled system at state z and time t, + * and store the sensitivities in dz_dt. * - * @param[in] y current state of the coupled ode. This must be the - * correct size, N_. - * @param[out] dy_dt populated with derivatives of the coupled - * system evaluated at specified state and time. This vector will be - * overwritten. - * @param[in] t time. + * For all arithmetic types the coupled system right hand side is the same + * as the regular ODE right hand side. + * + * @param[in] z State of coupled system + * @param[out] dz_dt Evaluation of right hand side of coupled system + * @param[in] t Time at which to evaluated right hand side * @throw exception if the system function does not return - * a derivative vector of the same size as the state vector. + * the expected number of derivatives, N. */ - void operator()(const std::vector& y, std::vector& dy_dt, + void operator()(const std::vector& z, std::vector& dz_dt, double t) const { - dy_dt = f_(t, y, theta_dbl_, x_, x_int_, msgs_); - check_size_match("coupled_ode_system", "y", y.size(), "dy_dt", - dy_dt.size()); + Eigen::VectorXd y(z.size()); + for (size_t n = 0; n < z.size(); ++n) + y.coeffRef(n) = z[n]; + + dz_dt.resize(y.size()); + + Eigen::VectorXd f_y_t + = apply([&](const Args&... args) { return f_(t, y, msgs_, args...); }, + args_tuple_); + + check_size_match("coupled_ode_system", "dy_dt", f_y_t.size(), "states", + y.size()); + + Eigen::Map(dz_dt.data(), dz_dt.size()) = f_y_t; } /** @@ -105,23 +82,38 @@ class coupled_ode_system { * * @return size of the coupled system. */ - int size() const { return size_; } + size_t size() const { return N_; } /** - * Returns the initial state of the coupled system. Here, it is - * identical to base ode state because the initial state is known. + * Returns the initial state of the coupled system. For arithmetic types + * this is the same as the regular ode system. * - * @return initial state of the coupled system + * @return the initial condition of the coupled system */ std::vector initial_state() const { - std::vector state(size_, 0.0); - for (size_t n = 0; n < N_; n++) { - state[n] = y0_dbl_[n]; + std::vector initial(size(), 0.0); + + for (size_t i = 0; i < N_; i++) { + initial[i] = value_of(y0_(i)); } - return state; + + return initial; } }; +template +struct coupled_ode_system + : public coupled_ode_system_impl< + std::is_arithmetic>::value, F, T_y0, + Args...> { + coupled_ode_system(const F& f, + const Eigen::Matrix& y0, + std::ostream* msgs, const Args&... args) + : coupled_ode_system_impl< + std::is_arithmetic>::value, F, T_y0, + Args...>(f, y0, msgs, args...) {} +}; + } // namespace math } // namespace stan #endif diff --git a/stan/math/prim/functor/integrate_ode_rk45.hpp b/stan/math/prim/functor/integrate_ode_rk45.hpp index bef2df9c5b3..1e568d6b1ea 100644 --- a/stan/math/prim/functor/integrate_ode_rk45.hpp +++ b/stan/math/prim/functor/integrate_ode_rk45.hpp @@ -2,159 +2,40 @@ #define STAN_MATH_PRIM_FUNCTOR_INTEGRATE_ODE_RK45_HPP #include -#include -#include -#include -#include -#include -#if BOOST_VERSION == 106400 -#include -#endif -#include -#include +#include +#include #include -#include -#include #include namespace stan { namespace math { /** - * Return the solutions for the specified system of ordinary - * differential equations given the specified initial state, - * initial times, times of desired solution, and parameters and - * data, writing error and warning messages to the specified - * stream. - * - * Warning: If the system of equations is stiff, roughly - * defined by having varying time scales across dimensions, then - * this solver is likely to be slow. - * - * This function is templated to allow the initial times to be - * either data or autodiff variables and the parameters to be data - * or autodiff variables. The autodiff-based implementation for - * reverse-mode are defined in namespace stan::math - * and may be invoked via argument-dependent lookup by including - * their headers. - * - * This function uses the Dormand-Prince - * method as implemented in Boost's - * boost::numeric::odeint::runge_kutta_dopri5 integrator. - * - * @tparam F type of ODE system function. - * @tparam T1 type of scalars for initial values. - * @tparam T2 type of scalars for parameters. - * @tparam T_t0 type of scalar of initial time point. - * @tparam T_ts type of time-points where ODE solution is returned. - * @param[in] f functor for the base ordinary differential equation. - * @param[in] y0 initial state. - * @param[in] t0 initial time. - * @param[in] ts times of the desired solutions, in strictly - * increasing order, all greater than the initial time. - * @param[in] theta parameter vector for the ODE. - * @param[in] x continuous data vector for the ODE. - * @param[in] x_int integer data vector for the ODE. - * @param[out] msgs the print stream for warning messages. - * @param[in] relative_tolerance relative tolerance parameter - * for Boost's ode solver. Defaults to 1e-6. - * @param[in] absolute_tolerance absolute tolerance parameter - * for Boost's ode solver. Defaults to 1e-6. - * @param[in] max_num_steps maximum number of steps to take within - * the Boost ode solver. - * @return a vector of states, each state being a vector of the - * same size as the state variable, corresponding to a time in ts. + * @deprecated use ode_rk45 */ -template -std::vector>> integrate_ode_rk45( - const F& f, const std::vector& y0, const T_t0& t0, - const std::vector& ts, const std::vector& theta, +template +inline auto integrate_ode_rk45( + const F& f, const std::vector& y0, const T_t0& t0, + const std::vector& ts, const std::vector& theta, const std::vector& x, const std::vector& x_int, std::ostream* msgs = nullptr, double relative_tolerance = 1e-6, - double absolute_tolerance = 1e-6, int max_num_steps = 1E6) { - using boost::numeric::odeint::integrate_times; - using boost::numeric::odeint::make_dense_output; - using boost::numeric::odeint::max_step_checker; - using boost::numeric::odeint::no_progress_error; - using boost::numeric::odeint::runge_kutta_dopri5; - - const double t0_dbl = value_of(t0); - const std::vector ts_dbl = value_of(ts); - - check_finite("integrate_ode_rk45", "initial state", y0); - check_finite("integrate_ode_rk45", "initial time", t0_dbl); - check_finite("integrate_ode_rk45", "times", ts_dbl); - check_finite("integrate_ode_rk45", "parameter vector", theta); - check_finite("integrate_ode_rk45", "continuous data", x); - - check_nonzero_size("integrate_ode_rk45", "initial state", y0); - check_nonzero_size("integrate_ode_rk45", "times", ts_dbl); - check_ordered("integrate_ode_rk45", "times", ts_dbl); - check_less("integrate_ode_rk45", "initial time", t0_dbl, ts_dbl[0]); - - if (relative_tolerance <= 0) { - invalid_argument("integrate_ode_rk45", "relative_tolerance,", - relative_tolerance, "", ", must be greater than 0"); - } - if (absolute_tolerance <= 0) { - invalid_argument("integrate_ode_rk45", "absolute_tolerance,", - absolute_tolerance, "", ", must be greater than 0"); - } - if (max_num_steps <= 0) { - invalid_argument("integrate_ode_rk45", "max_num_steps,", max_num_steps, "", - ", must be greater than 0"); - } - - // creates basic or coupled system by template specializations - coupled_ode_system coupled_system(f, y0, theta, x, x_int, msgs); - - // first time in the vector must be time of initial state - std::vector ts_vec(ts.size() + 1); - ts_vec[0] = t0_dbl; - std::copy(ts_dbl.begin(), ts_dbl.end(), ts_vec.begin() + 1); - - std::vector>> y; - coupled_ode_observer observer(f, y0, theta, t0, ts, x, - x_int, msgs, y); - bool observer_initial_recorded = false; - - // avoid recording of the initial state which is included by the - // conventions of odeint in the output - size_t timestep = 0; - auto filtered_observer - = [&](const std::vector& coupled_state, double t) -> void { - if (!observer_initial_recorded) { - observer_initial_recorded = true; - return; - } - observer(coupled_state, t); - timestep++; - }; - - // the coupled system creates the coupled initial state - std::vector initial_coupled_state = coupled_system.initial_state(); - - const double step_size = 0.1; - try { - integrate_times( - make_dense_output(absolute_tolerance, relative_tolerance, - runge_kutta_dopri5, double, - std::vector, double>()), - std::ref(coupled_system), initial_coupled_state, std::begin(ts_vec), - std::end(ts_vec), step_size, filtered_observer, - max_step_checker(max_num_steps)); - } catch (const no_progress_error& e) { - throw_domain_error("integrate_ode_rk45", "", ts_vec[timestep + 1], - "Failed to integrate to next output time (", - ") in less than max_num_steps steps"); - } - - return y; + double absolute_tolerance = 1e-6, int max_num_steps = 1e6) { + internal::integrate_ode_std_vector_interface_adapter f_adapted(f); + auto y = ode_rk45_tol_impl("integrate_ode_rk45", f_adapted, to_vector(y0), t0, + ts, relative_tolerance, absolute_tolerance, + max_num_steps, msgs, theta, x, x_int); + + std::vector>> + y_converted; + y_converted.reserve(y.size()); + for (size_t i = 0; i < y.size(); ++i) + y_converted.emplace_back(to_array_1d(y[i])); + + return y_converted; } } // namespace math - } // namespace stan #endif diff --git a/stan/math/prim/functor/integrate_ode_std_vector_interface_adapter.hpp b/stan/math/prim/functor/integrate_ode_std_vector_interface_adapter.hpp new file mode 100644 index 00000000000..9b0178e27b5 --- /dev/null +++ b/stan/math/prim/functor/integrate_ode_std_vector_interface_adapter.hpp @@ -0,0 +1,42 @@ +#ifndef STAN_MATH_PRIM_FUNCTOR_INTEGRATE_ODE_STD_VECTOR_INTERFACE_ADAPTER_HPP +#define STAN_MATH_PRIM_FUNCTOR_INTEGRATE_ODE_STD_VECTOR_INTERFACE_ADAPTER_HPP + +#include +#include +#include +#include + +namespace stan { +namespace math { + +namespace internal { + +/** + * Wrap a functor designed for use with integrate_ode_bdf, integrate_ode_rk45, + * and integrate_ode_adams to use with the new ode_bdf/ode_rk45 interfaces. + * + * The old functors took the ODE state as a std::vector. The new ones take + * state as an Eigen::Matrix. The adapter converts to and from these forms + * so that the old ODE interfaces can work. + */ +template +struct integrate_ode_std_vector_interface_adapter { + const F f_; + + explicit integrate_ode_std_vector_interface_adapter(const F& f) : f_(f) {} + + template + auto operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const std::vector& theta, + const std::vector& x, + const std::vector& x_int) const { + return to_vector(f_(t, to_array_1d(y), theta, x, x_int, msgs)); + } +}; + +} // namespace internal + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/prim/functor/ode_rk45.hpp b/stan/math/prim/functor/ode_rk45.hpp new file mode 100644 index 00000000000..b85a705553b --- /dev/null +++ b/stan/math/prim/functor/ode_rk45.hpp @@ -0,0 +1,241 @@ +#ifndef STAN_MATH_PRIM_FUNCTOR_ODE_RK45_HPP +#define STAN_MATH_PRIM_FUNCTOR_ODE_RK45_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace stan { +namespace math { + +/** + * Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of + * times, { t1, t2, t3, ... } using the non-stiff Runge-Kutta 45 solver in + * Boost. + * + * If the system of equations is stiff, ode_bdf will likely be + * faster. + * + * \p f must define an operator() with the signature as: + * template + * Eigen::Matrix, Eigen::Dynamic, 1> + * operator()(const T_t& t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_Args&... args); + * + * t is the time, y is the vector-valued state, msgs is a stream for error + * messages, and args are optional arguments passed to the ODE solve function + * (which are passed through to \p f without modification). + * + * @tparam F Type of ODE right hand side + * @tparam T_y0 Type of initial condition + * @tparam T_t0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param function_name Calling function name (for printing debugging messages) + * @param f Right hand side of the ODE + * @param y0_arg Initial state + * @param t0 Initial time + * @param ts Times at which to solve the ODE at. All values must be sorted and + * greater than t0. + * @param relative_tolerance Relative tolerance passed to Boost + * @param absolute_tolerance Absolute tolerance passed to Boost + * @param max_num_steps Upper limit on the number of integration steps to + * take between each output (error if exceeded) + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return Solution to ODE at times \p ts + */ +template +std::vector, + Eigen::Dynamic, 1>> +ode_rk45_tol_impl(const char* function_name, const F& f, + const Eigen::Matrix& y0_arg, T_t0 t0, + const std::vector& ts, double relative_tolerance, + double absolute_tolerance, + long int max_num_steps, // NOLINT(runtime/int) + std::ostream* msgs, const Args&... args) { + using boost::numeric::odeint::integrate_times; + using boost::numeric::odeint::make_dense_output; + using boost::numeric::odeint::max_step_checker; + using boost::numeric::odeint::no_progress_error; + using boost::numeric::odeint::runge_kutta_dopri5; + using boost::numeric::odeint::vector_space_algebra; + + using T_y0_t0 = return_type_t; + + Eigen::Matrix y0 + = y0_arg.template cast(); + + check_finite(function_name, "initial state", y0); + check_finite(function_name, "initial time", t0); + check_finite(function_name, "times", ts); + + // Code from https://stackoverflow.com/a/17340003 + std::vector unused_temp{ + 0, (check_finite(function_name, "ode parameters and data", args), 0)...}; + + check_nonzero_size(function_name, "initial state", y0); + check_nonzero_size(function_name, "times", ts); + check_sorted(function_name, "times", ts); + check_less(function_name, "initial time", t0, ts[0]); + + check_positive_finite(function_name, "relative_tolerance", + relative_tolerance); + check_positive_finite(function_name, "absolute_tolerance", + absolute_tolerance); + check_positive(function_name, "max_num_steps", max_num_steps); + + using return_t = return_type_t; + // creates basic or coupled system by template specializations + coupled_ode_system coupled_system(f, y0, msgs, args...); + + // first time in the vector must be time of initial state + std::vector ts_vec(ts.size() + 1); + ts_vec[0] = value_of(t0); + for (size_t i = 0; i < ts.size(); ++i) + ts_vec[i + 1] = value_of(ts[i]); + + std::vector> y; + y.reserve(ts.size()); + bool observer_initial_recorded = false; + size_t time_index = 0; + + // avoid recording of the initial state which is included by the + // conventions of odeint in the output + auto filtered_observer + = [&](const std::vector& coupled_state, double t) -> void { + if (!observer_initial_recorded) { + observer_initial_recorded = true; + return; + } + y.emplace_back(ode_store_sensitivities(f, coupled_state, y0, t0, + ts[time_index], msgs, args...)); + time_index++; + }; + + // the coupled system creates the coupled initial state + std::vector initial_coupled_state = coupled_system.initial_state(); + + const double step_size = 0.1; + try { + integrate_times( + make_dense_output(absolute_tolerance, relative_tolerance, + runge_kutta_dopri5, double, + std::vector, double>()), + std::ref(coupled_system), initial_coupled_state, std::begin(ts_vec), + std::end(ts_vec), step_size, filtered_observer, + max_step_checker(max_num_steps)); + } catch (const no_progress_error& e) { + throw_domain_error(function_name, "", ts_vec[time_index + 1], + "Failed to integrate to next output time (", + ") in less than max_num_steps steps"); + } + + return y; +} + +/** + * Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of + * times, { t1, t2, t3, ... } using the non-stiff Runge-Kutta 45 solver in + * Boost. + * + * If the system of equations is stiff, ode_bdf will likely be + * faster. + * + * \p f must define an operator() with the signature as: + * template + * Eigen::Matrix, Eigen::Dynamic, 1> + * operator()(const T_t& t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_Args&... args); + * + * t is the time, y is the vector-valued state, msgs is a stream for error + * messages, and args are optional arguments passed to the ODE solve function + * (which are passed through to \p f without modification). + * + * @tparam F Type of ODE right hand side + * @tparam T_0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param f Right hand side of the ODE + * @param y0_arg Initial state + * @param t0 Initial time + * @param ts Times at which to solve the ODE at. All values must be sorted and + * greater than t0. + * @param relative_tolerance Relative tolerance passed to Boost + * @param absolute_tolerance Absolute tolerance passed to Boost + * @param max_num_steps Upper limit on the number of integration steps to + * take between each output (error if exceeded) + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return Solution to ODE at times \p ts + */ +template +std::vector, + Eigen::Dynamic, 1>> +ode_rk45_tol(const F& f, const Eigen::Matrix& y0_arg, + T_t0 t0, const std::vector& ts, double relative_tolerance, + double absolute_tolerance, + long int max_num_steps, // NOLINT(runtime/int) + std::ostream* msgs, const Args&... args) { + return ode_rk45_tol_impl("ode_rk45_tol", f, y0_arg, t0, ts, + relative_tolerance, absolute_tolerance, + max_num_steps, msgs, args...); +} + +/** + * Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of + * times, { t1, t2, t3, ... } using the non-stiff Runge-Kutta 45 solver in Boost + * with defaults for relative_tolerance, absolute_tolerance, and max_num_steps. + * + * If the system of equations is stiff, ode_bdf will likely be + * faster. + * + * \p f must define an operator() with the signature as: + * template + * Eigen::Matrix, Eigen::Dynamic, 1> + * operator()(const T_t& t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_Args&... args); + * + * t is the time, y is the vector-valued state, msgs is a stream for error + * messages, and args are optional arguments passed to the ODE solve function + * (which are passed through to \p f without modification). + * + * @tparam F Type of ODE right hand side + * @tparam T_y0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param f Right hand side of the ODE + * @param y0 Initial state + * @param t0 Initial time + * @param ts Times at which to solve the ODE at. All values must be sorted and + * greather than t0. + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return Solution to ODE at times \p ts + */ +template +std::vector, + Eigen::Dynamic, 1>> +ode_rk45(const F& f, const Eigen::Matrix& y0, T_t0 t0, + const std::vector& ts, std::ostream* msgs, const Args&... args) { + double relative_tolerance = 1e-6; + double absolute_tolerance = 1e-6; + long int max_num_steps = 1e6; // NOLINT(runtime/int) + + return ode_rk45_tol_impl("ode_rk45", f, y0, t0, ts, relative_tolerance, + absolute_tolerance, max_num_steps, msgs, args...); +} + +} // namespace math +} // namespace stan +#endif diff --git a/stan/math/prim/functor/ode_store_sensitivities.hpp b/stan/math/prim/functor/ode_store_sensitivities.hpp new file mode 100644 index 00000000000..ec42a5506e4 --- /dev/null +++ b/stan/math/prim/functor/ode_store_sensitivities.hpp @@ -0,0 +1,45 @@ +#ifndef STAN_MATH_PRIM_FUNCTOR_ODE_STORE_SENSITIVITIES_HPP +#define STAN_MATH_PRIM_FUNCTOR_ODE_STORE_SENSITIVITIES_HPP + +#include +#include +#include +#include + +namespace stan { +namespace math { + +/** + * When all arguments are arithmetic, there are no sensitivities to store, so + * the function just returns the current coupled_state. + * + * @tparam F Type of ODE right hand side + * @tparam T_y0_t0 Type of initial state + * @tparam T_t0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param f Right hand side of the ODE + * @param coupled_state Current state of the coupled_ode_system + * @param y0 Initial state + * @param t0 Initial time + * @param t Times at which to solve the ODE at + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return ODE state + */ +template < + typename F, typename T_y0_t0, typename T_t0, typename T_t, typename... Args, + typename + = require_all_arithmetic_t...>> +Eigen::VectorXd ode_store_sensitivities( + const F& f, const std::vector& coupled_state, + const Eigen::Matrix& y0, T_t0 t0, T_t t, + std::ostream* msgs, const Args&... args) { + return Eigen::Map(coupled_state.data(), + coupled_state.size()); +} + +} // namespace math +} // namespace stan +#endif diff --git a/stan/math/rev/core.hpp b/stan/math/rev/core.hpp index 33eb934c08e..7ff988c98f0 100644 --- a/stan/math/rev/core.hpp +++ b/stan/math/rev/core.hpp @@ -67,5 +67,6 @@ #include #include #include +#include #endif diff --git a/stan/math/rev/core/accumulate_adjoints.hpp b/stan/math/rev/core/accumulate_adjoints.hpp index 98f0899119d..e5b27354ebd 100644 --- a/stan/math/rev/core/accumulate_adjoints.hpp +++ b/stan/math/rev/core/accumulate_adjoints.hpp @@ -29,7 +29,7 @@ template * = nullptr, typename... Pargs> inline double* accumulate_adjoints(double* dest, EigT&& x, Pargs&&... args); -template >* = nullptr, +template * = nullptr, typename... Pargs> inline double* accumulate_adjoints(double* dest, Arith&& x, Pargs&&... args); @@ -135,8 +135,7 @@ inline double* accumulate_adjoints(double* dest, EigT&& x, Pargs&&... args) { * @param args Further args to accumulate over * @return Final position of adjoint storage pointer */ -template >*, - typename... Pargs> +template *, typename... Pargs> inline double* accumulate_adjoints(double* dest, Arith&& x, Pargs&&... args) { return accumulate_adjoints(dest, std::forward(args)...); } diff --git a/stan/math/rev/core/save_varis.hpp b/stan/math/rev/core/save_varis.hpp index d97e8a1a474..46c1f48799d 100644 --- a/stan/math/rev/core/save_varis.hpp +++ b/stan/math/rev/core/save_varis.hpp @@ -29,7 +29,7 @@ template * = nullptr, typename... Pargs> inline vari** save_varis(vari** dest, EigT&& x, Pargs&&... args); -template >* = nullptr, +template * = nullptr, typename... Pargs> inline vari** save_varis(vari** dest, Arith&& x, Pargs&&... args); @@ -113,7 +113,7 @@ inline vari** save_varis(vari** dest, VecContainer&& x, Pargs&&... args) { template *, typename... Pargs> inline vari** save_varis(vari** dest, EigT&& x, Pargs&&... args) { for (int i = 0; i < x.size(); ++i) { - dest[i] = x(i).vi_; + dest[i] = x.coeffRef(i).vi_; } return save_varis(dest + x.size(), std::forward(args)...); } @@ -131,8 +131,7 @@ inline vari** save_varis(vari** dest, EigT&& x, Pargs&&... args) { * @param[in] args Additional arguments to have their varis saved * @return Final position of dest pointer */ -template >*, - typename... Pargs> +template *, typename... Pargs> inline vari** save_varis(vari** dest, Arith&& x, Pargs&&... args) { return save_varis(dest, std::forward(args)...); } diff --git a/stan/math/rev/core/zero_adjoints.hpp b/stan/math/rev/core/zero_adjoints.hpp new file mode 100644 index 00000000000..36368d443ee --- /dev/null +++ b/stan/math/rev/core/zero_adjoints.hpp @@ -0,0 +1,97 @@ +#ifndef STAN_MATH_REV_CORE_ZERO_ADJOINTS_HPP +#define STAN_MATH_REV_CORE_ZERO_ADJOINTS_HPP + +#include +#include +#include + +namespace stan { +namespace math { + +inline void zero_adjoints(); + +template * = nullptr> +inline void zero_adjoints(T& x, Pargs&... args); + +template +inline void zero_adjoints(var& x, Pargs&... args); + +template +inline void zero_adjoints(Eigen::Matrix& x, Pargs&... args); + +template * = nullptr> +inline void zero_adjoints(std::vector& x, Pargs&... args); + +/** + * End of recursion for set_zero_adjoints + */ +inline void zero_adjoints() {} + +/** + * Do nothing for non-autodiff arguments. Recursively call zero_adjoints + * on the rest of the arguments. + * + * @tparam T type of current argument + * @tparam Pargs type of rest of arguments + * + * @param x current argument + * @param args rest of arguments to zero + */ +template *> +inline void zero_adjoints(T& x, Pargs&... args) { + zero_adjoints(args...); +} + +/** + * Zero the adjoint of the vari in the first argument. Recursively call + * zero_adjoints on the rest of the arguments. + * + * @tparam T type of current argument + * @tparam Pargs type of rest of arguments + * + * @param x current argument + * @param args rest of arguments to zero + */ +template +inline void zero_adjoints(var& x, Pargs&... args) { + x.vi_->set_zero_adjoint(); + zero_adjoints(args...); +} + +/** + * Zero the adjoints of the varis of every var in an Eigen::Matrix + * container. Recursively call zero_adjoints on the rest of the arguments. + * + * @tparam T type of current argument + * @tparam Pargs type of rest of arguments + * + * @param x current argument + * @param args rest of arguments to zero + */ +template +inline void zero_adjoints(Eigen::Matrix& x, Pargs&... args) { + for (size_t i = 0; i < x.size(); ++i) + x.coeffRef(i).vi_->set_zero_adjoint(); + zero_adjoints(args...); +} + +/** + * Zero the adjoints of every element in a vector. Recursively call + * zero_adjoints on the rest of the arguments. + * + * @tparam T type of current argument + * @tparam Pargs type of rest of arguments + * + * @param x current argument + * @param args rest of arguments to zero + */ +template *> +inline void zero_adjoints(std::vector& x, Pargs&... args) { + for (size_t i = 0; i < x.size(); ++i) + zero_adjoints(x[i]); + zero_adjoints(args...); +} + +} // namespace math +} // namespace stan +#endif diff --git a/stan/math/rev/functor.hpp b/stan/math/rev/functor.hpp index 0892095ab47..bc21607b01b 100644 --- a/stan/math/rev/functor.hpp +++ b/stan/math/rev/functor.hpp @@ -9,13 +9,15 @@ #include #include #include -#include #include #include #include #include #include #include +#include +#include +#include #include #include #include diff --git a/stan/math/rev/functor/coupled_ode_system.hpp b/stan/math/rev/functor/coupled_ode_system.hpp index 22669207494..ac278e7665b 100644 --- a/stan/math/rev/functor/coupled_ode_system.hpp +++ b/stan/math/rev/functor/coupled_ode_system.hpp @@ -1,12 +1,12 @@ #ifndef STAN_MATH_REV_FUNCTOR_COUPLED_ODE_SYSTEM_HPP #define STAN_MATH_REV_FUNCTOR_COUPLED_ODE_SYSTEM_HPP -#include #include +#include +#include +#include #include #include -#include -#include #include #include #include @@ -15,326 +15,8 @@ namespace stan { namespace math { /** - * The coupled_ode_system template specialization - * for known initial values and unknown parameters. - * - *

This coupled ode system has N + N * M states where N is the size of - * the base ode system and M is the number of parameters. - * - *

For the coupled ode system, the first N states are the base - * system's states: \f$ \frac{d x_n}{dt} \f$. - * - *

The next M states correspond to the sensitivities of the - * parameters with respect to the first base system equation: - * \f[ - * \frac{d x_{N + m}}{dt} - * = \frac{d}{dt} \frac{\partial x_1}{\partial \theta_m} - * \f] - * for \f$ m \in {1, \ldots, M} \f$]. - * - *

The next M states correspond to the sensitivites with respect - * to the second base system equation, and so on through the last base - * system equation. - * - *

Note: Calculating the sensitivity system requires the Jacobian - * of the base ODE RHS wrt to the parameters theta. The parameter - * vector theta is constant for successive calls to the exposed - * operator(). For this reason, the parameter vector theta is copied - * upon construction onto the nochain var autodiff tape which is used - * in the the nested autodiff performed in the operator() of this - * adaptor. Doing so reduces the size of the nested autodiff and - * speeds up autodiff. As a side effect, the parameter vector theta - * will remain on the nochain autodiff part of the autodiff tape being - * in use even after destruction of the given instance. Moreover, the - * adjoint zeroing for the nested system does not cover the theta - * parameter vector part of the nochain autodiff tape and is therefore - * set to zero using a dedicated loop. - * - * @tparam F base ode system functor. Must provide - * operator()(double t, std::vector y, std::vector theta, - * std::vector x, std::vectorx_int, std::ostream* - * msgs) - */ -template -struct coupled_ode_system { - const F& f_; - const std::vector& y0_dbl_; - const std::vector& theta_; - std::vector theta_nochain_; - const std::vector& x_; - const std::vector& x_int_; - const size_t N_; - const size_t M_; - const size_t size_; - std::ostream* msgs_; - - /** - * Construct a coupled ode system from the base system function, - * initial state of the base system, parameters, and a stream for - * messages. - * - * @param[in] f the base ODE system functor - * @param[in] y0 the initial state of the base ode - * @param[in] theta parameters of the base ode - * @param[in] x real data - * @param[in] x_int integer data - * @param[in, out] msgs stream for messages - */ - coupled_ode_system(const F& f, const std::vector& y0, - const std::vector& theta, - const std::vector& x, - const std::vector& x_int, std::ostream* msgs) - : f_(f), - y0_dbl_(y0), - theta_(theta), - x_(x), - x_int_(x_int), - N_(y0.size()), - M_(theta.size()), - size_(N_ + N_ * M_), - msgs_(msgs) { - for (const var& p : theta) { - theta_nochain_.emplace_back(new vari(p.val(), false)); - } - } - - /** - * Calculates the derivative of the coupled ode system with respect - * to time. - * - * This method uses nested autodiff and is not thread safe. - * - * @param[in] z state of the coupled ode system; this must be size - * size() - * @param[out] dz_dt a vector of size size() with the - * derivatives of the coupled system with respect to time - * @param[in] t time - * @throw exception if the base ode function does not return the - * expected number of derivatives, N. - */ - void operator()(const std::vector& z, std::vector& dz_dt, - double t) const { - using std::vector; - - // Run nested autodiff in this scope - nested_rev_autodiff nested; - - vector y_vars(z.begin(), z.begin() + N_); - - vector dy_dt_vars = f_(t, y_vars, theta_nochain_, x_, x_int_, msgs_); - - check_size_match("coupled_ode_system", "dz_dt", dy_dt_vars.size(), "states", - N_); - - for (size_t i = 0; i < N_; i++) { - dz_dt[i] = dy_dt_vars[i].val(); - dy_dt_vars[i].grad(); - - for (size_t j = 0; j < M_; j++) { - // orders derivatives by equation (i.e. if there are 2 eqns - // (y1, y2) and 2 parameters (a, b), dy_dt will be ordered as: - // dy1_dt, dy2_dt, dy1_da, dy2_da, dy1_db, dy2_db - double temp_deriv = theta_nochain_[j].adj(); - const size_t offset = N_ + N_ * j; - for (size_t k = 0; k < N_; k++) { - temp_deriv += z[offset + k] * y_vars[k].adj(); - } - - dz_dt[offset + i] = temp_deriv; - } - - nested.set_zero_all_adjoints(); - // Parameters stored on the outer (non-nested) nochain stack are not - // reset to zero by the last call. This is done as a separate step here. - // See efficiency note above on template specialization for more details - // on this. - for (size_t j = 0; j < M_; ++j) { - theta_nochain_[j].vi_->set_zero_adjoint(); - } - } - } - - /** - * Returns the size of the coupled system. - * - * @return size of the coupled system. - */ - size_t size() const { return size_; } - - /** - * Returns the initial state of the coupled system. - * - * The initial state of the coupled ode system is the same - * as the base ode system. This is because the initial values - * are known. - * - * There are N + N * M coupled states, where N is the number of base - * ode system states and M is the number of parameters. The first N - * correspond to the initial values provided. The next N * M states - * are all 0. - * - * @return the initial condition of the coupled system, a vector of - * size N + N * M. - */ - std::vector initial_state() const { - std::vector state(size_, 0.0); - for (size_t n = 0; n < N_; n++) { - state[n] = y0_dbl_[n]; - } - return state; - } -}; - -/** - * The coupled_ode_system template specialization - * for unknown initial values and known parameters. - * - *

This coupled ode system has N + N * N states where N is the size - * of the base ode system. - * - *

For the coupled ode system, the first N states are the base - * system's states: \f$ \frac{d x_n}{dt} \f$. - * - *

The next N states correspond to the sensitivities of the - * initial conditions with respect to the first base system equation: - * \f[ - * \frac{d x_{N + n}}{dt} - * = \frac{d}{dt} \frac{\partial x_1}{\partial y0_m} - * \f] - * for \f$ n \in {1, \ldots, N} \f$]. - * - *

The next N states correspond to the sensitivites with respect - * to the second base system equation, and so on through the last base - * system equation. - * - * @tparam F base ode system functor. Must provide - * operator()(double t, std::vector y, std::vector theta, - * std::vector x, std::vectorx_int, std::ostream* - * msgs) - */ -template -struct coupled_ode_system { - const F& f_; - const std::vector& y0_; - const std::vector& theta_dbl_; - const std::vector& x_; - const std::vector& x_int_; - std::ostream* msgs_; - const size_t N_; - const size_t M_; - const size_t size_; - - /** - * Construct a coupled ode system from the base system function, - * initial state of the base system, parameters, and a stream for - * messages. - * - * @param[in] f the base ODE system functor - * @param[in] y0 the initial state of the base ode - * @param[in] theta parameters of the base ode - * @param[in] x real data - * @param[in] x_int integer data - * @param[in, out] msgs stream for messages - */ - coupled_ode_system(const F& f, const std::vector& y0, - const std::vector& theta, - const std::vector& x, - const std::vector& x_int, std::ostream* msgs) - : f_(f), - y0_(y0), - theta_dbl_(theta), - x_(x), - x_int_(x_int), - msgs_(msgs), - N_(y0.size()), - M_(theta.size()), - size_(N_ + N_ * N_) {} - - /** - * Calculates the derivative of the coupled ode system with respect - * to time. - * - * This method uses nested autodiff and is not thread safe. - * - * @param[in] z state of the coupled ode system; this must be - * size size() - * @param[out] dz_dt a vector of length size() with the - * derivatives of the coupled system with respect to time - * @param[in] t time - * @throw exception if the base ode function does not return the - * expected number of derivatives, N. - */ - void operator()(const std::vector& z, std::vector& dz_dt, - double t) const { - using std::vector; - - // Run nested autodiff in this scope - nested_rev_autodiff nested; - - vector y_vars(z.begin(), z.begin() + N_); - - vector dy_dt_vars = f_(t, y_vars, theta_dbl_, x_, x_int_, msgs_); - - check_size_match("coupled_ode_system", "dz_dt", dy_dt_vars.size(), "states", - N_); - - for (size_t i = 0; i < N_; i++) { - dz_dt[i] = dy_dt_vars[i].val(); - dy_dt_vars[i].grad(); - - for (size_t j = 0; j < N_; j++) { - // orders derivatives by equation (i.e. if there are 2 eqns - // (y1, y2) and 2 initial conditions (y0_a, y0_b), dy_dt will be - // ordered as: dy1_dt, dy2_dt, dy1_d{y0_a}, dy2_d{y0_a}, dy1_d{y0_b}, - // dy2_d{y0_b} - double temp_deriv = 0; - const size_t offset = N_ + N_ * j; - for (size_t k = 0; k < N_; k++) { - temp_deriv += z[offset + k] * y_vars[k].adj(); - } - - dz_dt[offset + i] = temp_deriv; - } - - nested.set_zero_all_adjoints(); - } - } - - /** - * Returns the size of the coupled system. - * - * @return size of the coupled system. - */ - size_t size() const { return size_; } - - /** - * Returns the initial state of the coupled system. - * - *

Because the starting state is unknown, the coupled system - * incorporates the initial conditions as parameters. At the initial - * time the Jacobian of the integrated function is the identity matrix. - * - * @return the initial condition of the coupled system. - * This is a vector of length size() where the first N values are - * the initial condition of the base ODE and the remainder - * correspond to the identity matrix which is the Jacobian of the - * integrated function at the initial time-point. - */ - std::vector initial_state() const { - std::vector initial(size_, 0.0); - for (size_t i = 0; i < N_; i++) { - initial[i] = value_of(y0_[i]); - } - for (size_t i = 0; i < N_; i++) { - initial[N_ + i * N_ + i] = 1.0; - } - return initial; - } -}; - -/** - * The coupled_ode_system template specialization - * for unknown initial values and unknown parameters. + * The coupled_ode_system_impl template specialization when + * the state or parameters are autodiff types. * *

This coupled ode system has N + (N + M) * N states where N is * the size of the base ode system and M is the number of parameters. @@ -344,7 +26,8 @@ struct coupled_ode_system { * *

The next N + M states correspond to the sensitivities of the * initial conditions, then to the sensitivities of the parameters - * with respect to the to the first base system equation: + * with respect to the to the first base system equation. Calling + * initial conditions \f$ y0 \f$ and parameters \f$ \theta \f$: * * \f[ * \frac{d x_{N + n}}{dt} @@ -362,35 +45,35 @@ struct coupled_ode_system { * so on through the last base system equation. * *

Note: Calculating the sensitivity system requires the Jacobian - * of the base ODE RHS wrt to the parameters theta. The parameter - * vector theta is constant for successive calls to the exposed - * operator(). For this reason, the parameter vector theta is copied - * upon construction onto the nochain var autodiff tape which is used - * in the the nested autodiff performed in the operator() of this - * adaptor. Doing so reduces the size of the nested autodiff and - * speeds up autodiff. As a side effect, the parameter vector theta + * of the base ODE RHS wrt to the parameters. The parameters are constant + * for successive calls to the exposed operator(). For this reason, + * the parameter vector theta is copied upon construction onto the nochain + * var autodiff tape which is used in the the nested autodiff performed + * in the operator() of this adaptor. Doing so reduces the size of the + * nested autodiff and speeds up autodiff. As a side effect, the parameters * will remain on the nochain autodiff part of the autodiff tape being * in use even after destruction of the given instance. Moreover, the * adjoint zeroing for the nested system does not cover the theta * parameter vector part of the nochain autodiff tape and is therefore - * set to zero using a dedicated loop. + * set to zero separately. * * @tparam F base ode system functor. Must provide - * operator()(double t, std::vector y, std::vector theta, - * std::vector x, std::vectorx_int, std::ostream* - * msgs) + * + * template + * operator()(double t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_args&... args) */ -template -struct coupled_ode_system { +template +struct coupled_ode_system_impl { const F& f_; - const std::vector& y0_; - const std::vector& theta_; - std::vector theta_nochain_; - const std::vector& x_; - const std::vector& x_int_; + const Eigen::Matrix& y0_; + std::tuple()))...> + local_args_tuple_; + const size_t num_y0_vars_; + const size_t num_args_vars; const size_t N_; - const size_t M_; - const size_t size_; + Eigen::VectorXd args_adjoints_; + Eigen::VectorXd y_adjoints_; std::ostream* msgs_; /** @@ -400,34 +83,25 @@ struct coupled_ode_system { * * @param[in] f the base ODE system functor * @param[in] y0 the initial state of the base ode - * @param[in] theta parameters of the base ode - * @param[in] x real data - * @param[in] x_int integer data * @param[in, out] msgs stream for messages + * @param[in] args other additional arguments */ - coupled_ode_system(const F& f, const std::vector& y0, - const std::vector& theta, - const std::vector& x, - const std::vector& x_int, std::ostream* msgs) + coupled_ode_system_impl(const F& f, + const Eigen::Matrix& y0, + std::ostream* msgs, const Args&... args) : f_(f), y0_(y0), - theta_(theta), - x_(x), - x_int_(x_int), + local_args_tuple_(deep_copy_vars(args)...), + num_y0_vars_(count_vars(y0_)), + num_args_vars(count_vars(args...)), N_(y0.size()), - M_(theta.size()), - size_(N_ + N_ * (N_ + M_)), - msgs_(msgs) { - for (const var& p : theta) { - theta_nochain_.emplace_back(new vari(p.val(), false)); - } - } + args_adjoints_(num_args_vars), + y_adjoints_(N_), + msgs_(msgs) {} /** - * Calculates the derivative of the coupled ode system with respect - * to time. - * - * This method uses nested autodiff and is not thread safe. + * Calculates the right hand side of the coupled ode system (the regular + * ode system with forward sensitivities). * * @param[in] z state of the coupled ode system; this must be size * size() @@ -438,53 +112,70 @@ struct coupled_ode_system { * expected number of derivatives, N. */ void operator()(const std::vector& z, std::vector& dz_dt, - double t) const { + double t) { using std::vector; + dz_dt.resize(size()); + // Run nested autodiff in this scope nested_rev_autodiff nested; - vector y_vars(z.begin(), z.begin() + N_); + Eigen::Matrix y_vars(N_); + for (size_t n = 0; n < N_; ++n) + y_vars.coeffRef(n) = z[n]; - vector dy_dt_vars = f_(t, y_vars, theta_nochain_, x_, x_int_, msgs_); + Eigen::Matrix f_y_t_vars + = apply([&](auto&&... args) { return f_(t, y_vars, msgs_, args...); }, + local_args_tuple_); - check_size_match("coupled_ode_system", "dz_dt", dy_dt_vars.size(), "states", + check_size_match("coupled_ode_system", "dy_dt", f_y_t_vars.size(), "states", N_); - for (size_t i = 0; i < N_; i++) { - dz_dt[i] = dy_dt_vars[i].val(); - dy_dt_vars[i].grad(); + for (size_t i = 0; i < N_; ++i) { + dz_dt[i] = f_y_t_vars.coeffRef(i).val(); + f_y_t_vars.coeffRef(i).grad(); - for (size_t j = 0; j < N_; j++) { - // orders derivatives by equation (i.e. if there are 2 eqns - // (y1, y2) and 2 parameters (a, b), dy_dt will be ordered as: - // dy1_dt, dy2_dt, dy1_da, dy2_da, dy1_db, dy2_db - double temp_deriv = 0; - const size_t offset = N_ + N_ * j; - for (size_t k = 0; k < N_; k++) { - temp_deriv += z[offset + k] * y_vars[k].adj(); - } + y_adjoints_ = y_vars.adj(); + + // memset was faster than Eigen setZero + memset(args_adjoints_.data(), 0, sizeof(double) * num_args_vars); - dz_dt[offset + i] = temp_deriv; + apply( + [&](auto&&... args) { + accumulate_adjoints(args_adjoints_.data(), args...); + }, + local_args_tuple_); + + // The vars here do not live on the nested stack so must be zero'd + // separately + apply([&](auto&&... args) { zero_adjoints(args...); }, local_args_tuple_); + + // No need to zero adjoints after last sweep + if (i + 1 < N_) { + nested.set_zero_all_adjoints(); } - for (size_t j = 0; j < M_; j++) { - double temp_deriv = theta_nochain_[j].adj(); - const size_t offset = N_ + N_ * N_ + N_ * j; - for (size_t k = 0; k < N_; k++) { - temp_deriv += z[offset + k] * y_vars[k].adj(); + // Compute the right hand side for the sensitivities with respect to the + // initial conditions + for (size_t j = 0; j < num_y0_vars_; ++j) { + double temp_deriv = 0.0; + for (size_t k = 0; k < N_; ++k) { + temp_deriv += z[N_ + N_ * j + k] * y_adjoints_.coeffRef(k); } - dz_dt[offset + i] = temp_deriv; + dz_dt[N_ + N_ * j + i] = temp_deriv; } - nested.set_zero_all_adjoints(); - // Parameters stored on the outer (non-nested) nochain stack are not - // reset to zero by the last call. This is done as a separate step here. - // See efficiency note above on template specialization for more details - // on this. - for (size_t j = 0; j < M_; ++j) { - theta_nochain_[j].vi_->set_zero_adjoint(); + // Compute the right hand size for the sensitivities with respect to the + // parameters + for (size_t j = 0; j < num_args_vars; ++j) { + double temp_deriv = args_adjoints_.coeffRef(j); + for (size_t k = 0; k < N_; ++k) { + temp_deriv += z[N_ + N_ * num_y0_vars_ + N_ * j + k] + * y_adjoints_.coeffRef(k); + } + + dz_dt[N_ + N_ * num_y0_vars_ + N_ * j + i] = temp_deriv; } } } @@ -494,7 +185,7 @@ struct coupled_ode_system { * * @return size of the coupled system. */ - size_t size() const { return size_; } + size_t size() const { return N_ + N_ * num_y0_vars_ + N_ * num_args_vars; } /** * Returns the initial state of the coupled system. @@ -515,11 +206,11 @@ struct coupled_ode_system { * parameters at the initial time-point, which is zero. */ std::vector initial_state() const { - std::vector initial(size_, 0.0); + std::vector initial(size(), 0.0); for (size_t i = 0; i < N_; i++) { - initial[i] = value_of(y0_[i]); + initial[i] = value_of(y0_(i)); } - for (size_t i = 0; i < N_; i++) { + for (size_t i = 0; i < num_y0_vars_; i++) { initial[N_ + i * N_ + i] = 1.0; } return initial; diff --git a/stan/math/rev/functor/cvodes_integrator.hpp b/stan/math/rev/functor/cvodes_integrator.hpp index 0630f933c21..70b33f21740 100644 --- a/stan/math/rev/functor/cvodes_integrator.hpp +++ b/stan/math/rev/functor/cvodes_integrator.hpp @@ -2,14 +2,13 @@ #define STAN_MATH_REV_FUNCTOR_INTEGRATE_ODE_CVODES_HPP #include -#include #include -#include +#include +#include #include #include -#include -#include #include +#include #include #include #include @@ -23,50 +22,159 @@ namespace math { * methods). * * @tparam Lmm ID of ODE solver (1: ADAMS, 2: BDF) + * @tparam F Type of ODE right hand side + * @tparam T_y0 Type of scalars for initial state + * @tparam T_param Type of scalars for parameters + * @tparam T_t0 Type of scalar of initial time point + * @tparam T_ts Type of time-points where ODE solution is returned */ -template +template class cvodes_integrator { - public: - cvodes_integrator() {} + using T_Return = return_type_t; + using T_y0_t0 = return_type_t; + + const char* function_name_; + const F& f_; + const Eigen::Matrix y0_; + const T_t0 t0_; + const std::vector& ts_; + std::tuple args_tuple_; + std::tuple()))>...> + value_of_args_tuple_; + const size_t N_; + std::ostream* msgs_; + double relative_tolerance_; + double absolute_tolerance_; + long int max_num_steps_; // NOLINT(runtime/int) + + const size_t num_y0_vars_; + const size_t num_args_vars_; + + coupled_ode_system coupled_ode_; + + std::vector coupled_state_; + N_Vector nv_state_; + N_Vector* nv_state_sens_; + SUNMatrix A_; + SUNLinearSolver LS_; /** - * Return the solutions for the specified system of ordinary - * differential equations given the specified initial state, - * initial times, times of desired solution, and parameters and - * data, writing error and warning messages to the specified - * stream. - * - * This function is templated to allow the initials to be - * either data or autodiff variables and the parameters to be data - * or autodiff variables. The autodiff-based implementation for - * reverse-mode are defined in namespace stan::math - * and may be invoked via argument-dependent lookup by including - * their headers. - * - * The solver used is based on the backward differentiation - * formula which is an implicit numerical integration scheme - * appropriate for stiff ODE systems. - * - * @tparam F type of ODE system function. - * @tparam T_initial type of scalars for initial values. - * @tparam T_param type of scalars for parameters. - * @tparam T_t0 type of scalar of initial time point. - * @tparam T_ts type of time-points where ODE solution is returned. + * Implements the function of type CVRhsFn which is the user-defined + * ODE RHS passed to CVODES. + */ + static int cv_rhs(realtype t, N_Vector y, N_Vector ydot, void* user_data) { + cvodes_integrator* integrator = static_cast(user_data); + integrator->rhs(t, NV_DATA_S(y), NV_DATA_S(ydot)); + return 0; + } + + /** + * Implements the function of type CVSensRhsFn which is the + * RHS of the sensitivity ODE system. + */ + static int cv_rhs_sens(int Ns, realtype t, N_Vector y, N_Vector ydot, + N_Vector* yS, N_Vector* ySdot, void* user_data, + N_Vector tmp1, N_Vector tmp2) { + cvodes_integrator* integrator = static_cast(user_data); + integrator->rhs_sens(t, NV_DATA_S(y), yS, ySdot); + return 0; + } + + /** + * Implements the function of type CVDlsJacFn which is the + * user-defined callback for CVODES to calculate the jacobian of the + * ode_rhs wrt to the states y. The jacobian is stored in column + * major format. + */ + static int cv_jacobian_states(realtype t, N_Vector y, N_Vector fy, + SUNMatrix J, void* user_data, N_Vector tmp1, + N_Vector tmp2, N_Vector tmp3) { + cvodes_integrator* integrator = static_cast(user_data); + integrator->jacobian_states(t, NV_DATA_S(y), J); + return 0; + } + + /** + * Calculates the ODE RHS, dy_dt, using the user-supplied functor at + * the given time t and state y. + */ + inline void rhs(double t, const double y[], double dy_dt[]) const { + const Eigen::VectorXd y_vec = Eigen::Map(y, N_); + + Eigen::VectorXd dy_dt_vec + = apply([&](auto&&... args) { return f_(t, y_vec, msgs_, args...); }, + value_of_args_tuple_); + + check_size_match("cvodes_integrator", "dy_dt", dy_dt_vec.size(), "states", + N_); + + std::copy(dy_dt_vec.data(), dy_dt_vec.data() + dy_dt_vec.size(), dy_dt); + } + + /** + * Calculates the jacobian of the ODE RHS wrt to its states y at the + * given time-point t and state y. + */ + inline void jacobian_states(double t, const double y[], SUNMatrix J) const { + Eigen::VectorXd fy; + Eigen::MatrixXd Jfy; + + auto f_wrapped = [&](const Eigen::Matrix& y) { + return apply([&](auto&&... args) { return f_(t, y, msgs_, args...); }, + value_of_args_tuple_); + }; + + jacobian(f_wrapped, Eigen::Map(y, N_), fy, Jfy); + + for (size_t j = 0; j < Jfy.cols(); ++j) { + for (size_t i = 0; i < Jfy.rows(); ++i) { + SM_ELEMENT_D(J, i, j) = Jfy(i, j); + } + } + } + + /** + * Calculates the RHS of the sensitivity ODE system which + * corresponds to the coupled ode system from which the first N + * states are omitted, since the first N states are the ODE RHS + * which CVODES separates from the main ODE RHS. + */ + inline void rhs_sens(double t, const double y[], N_Vector* yS, + N_Vector* ySdot) { + std::vector z(coupled_state_.size()); + std::vector dz_dt; + std::copy(y, y + N_, z.data()); + for (std::size_t s = 0; s < num_y0_vars_ + num_args_vars_; s++) { + std::copy(NV_DATA_S(yS[s]), NV_DATA_S(yS[s]) + N_, + z.data() + (s + 1) * N_); + } + coupled_ode_(z, dz_dt, t); + for (std::size_t s = 0; s < num_y0_vars_ + num_args_vars_; s++) { + std::move(dz_dt.data() + (s + 1) * N_, dz_dt.data() + (s + 2) * N_, + NV_DATA_S(ySdot[s])); + } + } + + public: + /** + * Construct cvodes_integrator object * - * @param[in] function_name name of function for error messages - * @param[in] f functor for the base ordinary differential equation. - * @param[in] y0 initial state. - * @param[in] t0 initial time. - * @param[in] ts times of the desired solutions, in strictly - * increasing order, all greater than the initial time. - * @param[in] theta parameter vector for the ODE. - * @param[in] x continuous data vector for the ODE. - * @param[in] x_int integer data vector for the ODE. - * @param[in, out] msgs the print stream for warning messages. - * @param[in] relative_tolerance relative tolerance passed to CVODE. - * @param[in] absolute_tolerance absolute tolerance passed to CVODE. - * @param[in] max_num_steps maximal number of admissable steps - * between time-points + * @param function_name Calling function name (for printing debugging + * messages) + * @param f Right hand side of the ODE + * @param y0 Initial state + * @param t0 Initial time + * @param ts Times at which to solve the ODE at. All values must be sorted and + * not less than t0. + * @param relative_tolerance Relative tolerance passed to CVODES + * @param absolute_tolerance Absolute tolerance passed to CVODES + * @param max_num_steps Upper limit on the number of integration steps to + * take between each output (error if exceeded) + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand + * side function + * @return Solution to ODE at times \p ts * @return a vector of states, each state being a vector of the * same size as the state variable, corresponding to a time in ts. * @throw std::domain_error if y0, t0, ts, theta, x are not @@ -75,116 +183,156 @@ class cvodes_integrator { * @throw std::invalid_argument if arguments are the wrong * size or tolerances or max_num_steps are out of range. */ - template - std::vector>> - integrate(const char* function_name, const F& f, - const std::vector& y0, const T_t0& t0, - const std::vector& ts, const std::vector& theta, - const std::vector& x, const std::vector& x_int, - std::ostream* msgs, double relative_tolerance, - double absolute_tolerance, - long int max_num_steps) { // NOLINT(runtime/int) - using initial_var = stan::is_var; - using param_var = stan::is_var; - - const double t0_dbl = value_of(t0); - const std::vector ts_dbl = value_of(ts); - - check_finite(function_name, "initial state", y0); - check_finite(function_name, "initial time", t0_dbl); - check_finite(function_name, "times", ts_dbl); - check_finite(function_name, "parameter vector", theta); - check_finite(function_name, "continuous data", x); - check_nonzero_size(function_name, "times", ts); - check_nonzero_size(function_name, "initial state", y0); - check_ordered(function_name, "times", ts_dbl); - check_less(function_name, "initial time", t0_dbl, ts_dbl[0]); - if (relative_tolerance <= 0) { - invalid_argument(function_name, "relative_tolerance,", relative_tolerance, - "", ", must be greater than 0"); - } - if (absolute_tolerance <= 0) { - invalid_argument(function_name, "absolute_tolerance,", absolute_tolerance, - "", ", must be greater than 0"); - } - if (max_num_steps <= 0) { - invalid_argument(function_name, "max_num_steps,", max_num_steps, "", - ", must be greater than 0"); + cvodes_integrator(const char* function_name, const F& f, + const Eigen::Matrix& y0, + const T_t0& t0, const std::vector& ts, + double relative_tolerance, double absolute_tolerance, + long int max_num_steps, // NOLINT(runtime/int) + std::ostream* msgs, const T_Args&... args) + : function_name_(function_name), + f_(f), + y0_(y0.template cast()), + t0_(t0), + ts_(ts), + args_tuple_(args...), + value_of_args_tuple_(value_of(args)...), + N_(y0.size()), + msgs_(msgs), + relative_tolerance_(relative_tolerance), + absolute_tolerance_(absolute_tolerance), + max_num_steps_(max_num_steps), + num_y0_vars_(count_vars(y0_)), + num_args_vars_(count_vars(args...)), + coupled_ode_(f, y0_, msgs, args...), + coupled_state_(coupled_ode_.initial_state()) { + check_finite(function_name, "initial state", y0_); + check_finite(function_name, "initial time", t0_); + check_finite(function_name, "times", ts_); + + // Code from: https://stackoverflow.com/a/17340003 . Should probably do + // something better + apply( + [&](auto&&... args) { + std::vector unused_temp{ + 0, (check_finite(function_name, "ode parameters and data", args), + 0)...}; + }, + args_tuple_); + + check_nonzero_size(function_name, "times", ts_); + check_nonzero_size(function_name, "initial state", y0_); + check_sorted(function_name, "times", ts_); + check_less(function_name, "initial time", t0_, ts_[0]); + check_positive_finite(function_name, "relative_tolerance", + relative_tolerance_); + check_positive_finite(function_name, "absolute_tolerance", + absolute_tolerance_); + check_positive(function_name, "max_num_steps", max_num_steps_); + + nv_state_ = N_VMake_Serial(N_, &coupled_state_[0]); + nv_state_sens_ = nullptr; + A_ = SUNDenseMatrix(N_, N_); + LS_ = SUNDenseLinearSolver(nv_state_, A_); + + if (num_y0_vars_ + num_args_vars_ > 0) { + nv_state_sens_ = N_VCloneVectorArrayEmpty_Serial( + num_y0_vars_ + num_args_vars_, nv_state_); + for (std::size_t i = 0; i < num_y0_vars_ + num_args_vars_; i++) { + NV_DATA_S(nv_state_sens_[i]) = &coupled_state_[N_] + i * N_; + } } + } - const size_t N = y0.size(); - const size_t M = theta.size(); - const size_t S = (initial_var::value ? N : 0) + (param_var::value ? M : 0); + ~cvodes_integrator() { + SUNLinSolFree(LS_); + SUNMatDestroy(A_); + N_VDestroy_Serial(nv_state_); + if (num_y0_vars_ + num_args_vars_ > 0) { + N_VDestroyVectorArray_Serial(nv_state_sens_, + num_y0_vars_ + num_args_vars_); + } + } - using ode_data = cvodes_ode_data; - ode_data cvodes_data(f, y0, theta, x, x_int, msgs); + /** + * Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of + * times, { t1, t2, t3, ... } using the stiff backward differentiation formula + * (BDF) solver in CVODES. + * + * @return std::vector of Eigen::Matrix of the states of the ODE, one for each + * solution time (excluding the initial state) + */ + std::vector> operator()() { + std::vector> y; void* cvodes_mem = CVodeCreate(Lmm); if (cvodes_mem == nullptr) { throw std::runtime_error("CVodeCreate failed to allocate memory"); } - const size_t coupled_size = cvodes_data.coupled_ode_.size(); - - std::vector>> y; - coupled_ode_observer observer( - f, y0, theta, t0, ts, x, x_int, msgs, y); - try { - check_flag_sundials(CVodeInit(cvodes_mem, &ode_data::cv_rhs, t0_dbl, - cvodes_data.nv_state_), + check_flag_sundials(CVodeInit(cvodes_mem, &cvodes_integrator::cv_rhs, + value_of(t0_), nv_state_), "CVodeInit"); // Assign pointer to this as user data check_flag_sundials( - CVodeSetUserData(cvodes_mem, reinterpret_cast(&cvodes_data)), + CVodeSetUserData(cvodes_mem, reinterpret_cast(this)), "CVodeSetUserData"); - cvodes_set_options(cvodes_mem, relative_tolerance, absolute_tolerance, - max_num_steps); + cvodes_set_options(cvodes_mem, relative_tolerance_, absolute_tolerance_, + max_num_steps_); - // for the stiff solvers we need to reserve additional memory - // and provide a Jacobian function call. new API since 3.0.0: - // create matrix object and linear solver object; resource - // (de-)allocation is handled in the cvodes_ode_data + check_flag_sundials(CVodeSetLinearSolver(cvodes_mem, LS_, A_), + "CVodeSetLinearSolver"); check_flag_sundials( - CVodeSetLinearSolver(cvodes_mem, cvodes_data.LS_, cvodes_data.A_), - "CVodeSetLinearSolver"); - check_flag_sundials( - CVodeSetJacFn(cvodes_mem, &ode_data::cv_jacobian_states), + CVodeSetJacFn(cvodes_mem, &cvodes_integrator::cv_jacobian_states), "CVodeSetJacFn"); // initialize forward sensitivity system of CVODES as needed - if (S > 0) { + if (num_y0_vars_ + num_args_vars_ > 0) { check_flag_sundials( - CVodeSensInit(cvodes_mem, static_cast(S), CV_STAGGERED, - &ode_data::cv_rhs_sens, cvodes_data.nv_state_sens_), + CVodeSensInit( + cvodes_mem, static_cast(num_y0_vars_ + num_args_vars_), + CV_STAGGERED, &cvodes_integrator::cv_rhs_sens, nv_state_sens_), "CVodeSensInit"); + check_flag_sundials(CVodeSetSensErrCon(cvodes_mem, SUNTRUE), + "CVodeSetSensErrCon"); + check_flag_sundials(CVodeSensEEtolerances(cvodes_mem), "CVodeSensEEtolerances"); } - double t_init = t0_dbl; - for (size_t n = 0; n < ts.size(); ++n) { - double t_final = ts_dbl[n]; + double t_init = value_of(t0_); + for (size_t n = 0; n < ts_.size(); ++n) { + double t_final = value_of(ts_[n]); + if (t_final != t_init) { - int ret = CVode(cvodes_mem, t_final, cvodes_data.nv_state_, &t_init, - CV_NORMAL); + int error_code + = CVode(cvodes_mem, t_final, nv_state_, &t_init, CV_NORMAL); - if (ret == CV_TOO_MUCH_WORK) { - throw_domain_error(function_name, "", t_final, + if (error_code == CV_TOO_MUCH_WORK) { + throw_domain_error(function_name_, "", t_final, "Failed to integrate to next output time (", ") in less than max_num_steps steps"); + } else { + check_flag_sundials(error_code, "CVode"); + } + + if (num_y0_vars_ + num_args_vars_ > 0) { + check_flag_sundials( + CVodeGetSens(cvodes_mem, &t_init, nv_state_sens_), + "CVodeGetSens"); } } - if (S > 0) { - check_flag_sundials( - CVodeGetSens(cvodes_mem, &t_init, cvodes_data.nv_state_sens_), - "CVodeGetSens"); - } - observer(cvodes_data.coupled_state_, t_final); + + y.emplace_back(apply( + [&](auto&&... args) { + return ode_store_sensitivities(f_, coupled_state_, y0_, t0_, + ts_[n], msgs_, args...); + }, + args_tuple_)); + t_init = t_final; } } catch (const std::exception& e) { diff --git a/stan/math/rev/functor/cvodes_ode_data.hpp b/stan/math/rev/functor/cvodes_ode_data.hpp deleted file mode 100644 index ded14b66668..00000000000 --- a/stan/math/rev/functor/cvodes_ode_data.hpp +++ /dev/null @@ -1,203 +0,0 @@ -#ifndef STAN_MATH_REV_FUNCTOR_CVODES_ODE_DATA_HPP -#define STAN_MATH_REV_FUNCTOR_CVODES_ODE_DATA_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace stan { -namespace math { - -/** - * CVODES ode data holder object which is used during CVODES - * integration for CVODES callbacks. - * - * @tparam F type of functor for the base ode system. - * @tparam T_initial type of initial values - * @tparam T_param type of parameters - */ -template -class cvodes_ode_data { - const F& f_; - const std::vector& y0_; - const std::vector& theta_; - const std::vector theta_dbl_; - const size_t N_; - const size_t M_; - const std::vector& x_; - const std::vector& x_int_; - std::ostream* msgs_; - const size_t S_; - - using ode_data = cvodes_ode_data; - using initial_var = stan::is_var; - using param_var = stan::is_var; - - public: - const coupled_ode_system coupled_ode_; - std::vector coupled_state_; - N_Vector nv_state_; - N_Vector* nv_state_sens_; - SUNMatrix A_; - SUNLinearSolver LS_; - - /** - * Construct CVODES ode data object to enable callbacks from - * CVODES during ODE integration. Static callbacks are defined - * for the ODE RHS (cv_rhs), the ODE sensitivity - * RHS (cv_rhs_sens) and for the ODE Jacobian wrt - * to the states (cv_jacobian_states). - * - * The callbacks required by CVODES are detailed in - * https://computation.llnl.gov/sites/default/files/public/cvs_guide.pdf - * - * Note: The supplied callbacks do always return 0 which flags to - * CVODES that the function was successfully evaluated. Errors are - * handled within Stan using exceptions such that any thrown error - * leads to the termination of the ODE integration. - * - * @param[in] f ode functor. - * @param[in] y0 initial state of the base ode. - * @param[in] theta parameters of the base ode. - * @param[in] x continuous data vector for the ODE. - * @param[in] x_int integer data vector for the ODE. - * @param[in] msgs stream to which messages are printed. - */ - cvodes_ode_data(const F& f, const std::vector& y0, - const std::vector& theta, - const std::vector& x, const std::vector& x_int, - std::ostream* msgs) - : f_(f), - y0_(y0), - theta_(theta), - theta_dbl_(value_of(theta)), - N_(y0.size()), - M_(theta.size()), - x_(x), - x_int_(x_int), - msgs_(msgs), - S_((initial_var::value ? N_ : 0) + (param_var::value ? M_ : 0)), - coupled_ode_(f, y0, theta, x, x_int, msgs), - coupled_state_(coupled_ode_.initial_state()), - nv_state_(N_VMake_Serial(N_, &coupled_state_[0])), - nv_state_sens_(nullptr), - A_(SUNDenseMatrix(N_, N_)), - LS_(SUNDenseLinearSolver(nv_state_, A_)) { - if (S_ > 0) { - nv_state_sens_ = N_VCloneVectorArrayEmpty_Serial(S_, nv_state_); - for (std::size_t i = 0; i < S_; i++) { - NV_DATA_S(nv_state_sens_[i]) = &coupled_state_[N_] + i * N_; - } - } - } - - ~cvodes_ode_data() { - SUNLinSolFree(LS_); - SUNMatDestroy(A_); - N_VDestroy_Serial(nv_state_); - if (S_ > 0) { - N_VDestroyVectorArray_Serial(nv_state_sens_, S_); - } - } - - /** - * Implements the function of type CVRhsFn which is the user-defined - * ODE RHS passed to CVODES. - */ - static int cv_rhs(realtype t, N_Vector y, N_Vector ydot, void* user_data) { - const ode_data* explicit_ode = static_cast(user_data); - explicit_ode->rhs(t, NV_DATA_S(y), NV_DATA_S(ydot)); - return 0; - } - - /** - * Implements the function of type CVSensRhsFn which is the - * RHS of the sensitivity ODE system. - */ - static int cv_rhs_sens(int Ns, realtype t, N_Vector y, N_Vector ydot, - N_Vector* yS, N_Vector* ySdot, void* user_data, - N_Vector tmp1, N_Vector tmp2) { - const ode_data* explicit_ode = static_cast(user_data); - explicit_ode->rhs_sens(t, NV_DATA_S(y), yS, ySdot); - return 0; - } - - /** - * Implements the function of type CVDlsJacFn which is the - * user-defined callback for CVODES to calculate the jacobian of the - * ode_rhs wrt to the states y. The jacobian is stored in column - * major format. - */ - static int cv_jacobian_states(realtype t, N_Vector y, N_Vector fy, - SUNMatrix J, void* user_data, N_Vector tmp1, - N_Vector tmp2, N_Vector tmp3) { - const ode_data* explicit_ode = static_cast(user_data); - return explicit_ode->jacobian_states(t, NV_DATA_S(y), J); - } - - private: - /** - * Calculates the ODE RHS, dy_dt, using the user-supplied functor at - * the given time t and state y. - */ - inline void rhs(double t, const double y[], double dy_dt[]) const { - const std::vector y_vec(y, y + N_); - const std::vector& dy_dt_vec - = f_(t, y_vec, theta_dbl_, x_, x_int_, msgs_); - check_size_match("cvodes_ode_data", "dz_dt", dy_dt_vec.size(), "states", - N_); - std::move(dy_dt_vec.begin(), dy_dt_vec.end(), dy_dt); - } - - /** - * Calculates the jacobian of the ODE RHS wrt to its states y at the - * given time-point t and state y. - * Note that the jacobian of the ODE system is the coupled ode system for - * varying states evaluated at the state y whenever we choose state - * y to be the initial of the coupled ode system. - */ - inline int jacobian_states(double t, const double y[], SUNMatrix J) const { - // Run nested autodiff in this scope - nested_rev_autodiff nested; - - const std::vector y_vec_var(y, y + N_); - coupled_ode_system ode_jacobian(f_, y_vec_var, theta_dbl_, - x_, x_int_, msgs_); - std::vector&& jacobian_y = std::vector(ode_jacobian.size()); - ode_jacobian(ode_jacobian.initial_state(), jacobian_y, t); - std::move(jacobian_y.begin() + N_, jacobian_y.end(), SM_DATA_D(J)); - return 0; - } - - /** - * Calculates the RHS of the sensitivity ODE system which - * corresponds to the coupled ode system from which the first N - * states are omitted, since the first N states are the ODE RHS - * which CVODES separates from the main ODE RHS. - */ - inline void rhs_sens(double t, const double y[], N_Vector* yS, - N_Vector* ySdot) const { - std::vector z(coupled_state_.size()); - std::vector&& dz_dt = std::vector(coupled_state_.size()); - std::copy(y, y + N_, z.begin()); - for (std::size_t s = 0; s < S_; s++) { - std::copy(NV_DATA_S(yS[s]), NV_DATA_S(yS[s]) + N_, - z.begin() + (s + 1) * N_); - } - coupled_ode_(z, dz_dt, t); - for (std::size_t s = 0; s < S_; s++) { - std::move(dz_dt.begin() + (s + 1) * N_, dz_dt.begin() + (s + 2) * N_, - NV_DATA_S(ySdot[s])); - } - } -}; - -} // namespace math -} // namespace stan -#endif diff --git a/stan/math/rev/functor/cvodes_utils.hpp b/stan/math/rev/functor/cvodes_utils.hpp index 7b5602a0ee6..d747f223959 100644 --- a/stan/math/rev/functor/cvodes_utils.hpp +++ b/stan/math/rev/functor/cvodes_utils.hpp @@ -10,18 +10,22 @@ namespace stan { namespace math { -// no-op error handler to silence CVodes error output; errors handled -// directly by Stan -extern "C" inline void cvodes_silent_err_handler(int error_code, - const char* module, - const char* function, - char* msg, void* eh_data) {} +extern "C" inline void cvodes_err_handler(int error_code, const char* module, + const char* function, char* msg, + void* eh_data) { + if (error_code != CV_TOO_MUCH_WORK) { + std::ostringstream msg1; + msg1 << msg << " Error code: "; + + throw_domain_error(module, function, error_code, msg1.str().c_str()); + } +} inline void cvodes_set_options(void* cvodes_mem, double rel_tol, double abs_tol, // NOLINTNEXTLINE(runtime/int) long int max_num_steps) { // forward CVode errors to noop error handler - CVodeSetErrHandlerFn(cvodes_mem, cvodes_silent_err_handler, nullptr); + CVodeSetErrHandlerFn(cvodes_mem, cvodes_err_handler, nullptr); // Initialize solver parameters check_flag_sundials(CVodeSStolerances(cvodes_mem, rel_tol, abs_tol), diff --git a/stan/math/rev/functor/integrate_ode_adams.hpp b/stan/math/rev/functor/integrate_ode_adams.hpp index 9eca96251d5..0cba70a321e 100644 --- a/stan/math/rev/functor/integrate_ode_adams.hpp +++ b/stan/math/rev/functor/integrate_ode_adams.hpp @@ -2,28 +2,39 @@ #define STAN_MATH_REV_FUNCTOR_INTEGRATE_ODE_ADAMS_HPP #include -#include +#include +#include #include #include namespace stan { namespace math { -template ode_adams + */ +template -std::vector>> -integrate_ode_adams(const F& f, const std::vector& y0, - const T_t0& t0, const std::vector& ts, +std::vector>> +integrate_ode_adams(const F& f, const std::vector& y0, const T_t0& t0, + const std::vector& ts, const std::vector& theta, const std::vector& x, const std::vector& x_int, std::ostream* msgs = nullptr, double relative_tolerance = 1e-10, double absolute_tolerance = 1e-10, long int max_num_steps = 1e8) { // NOLINT(runtime/int) - stan::math::cvodes_integrator integrator; - return integrator.integrate("integrate_ode_adams", f, y0, t0, ts, theta, x, - x_int, msgs, relative_tolerance, - absolute_tolerance, max_num_steps); + internal::integrate_ode_std_vector_interface_adapter f_adapted(f); + auto y = ode_adams_tol_impl("integrate_ode_adams", f_adapted, to_vector(y0), + t0, ts, relative_tolerance, absolute_tolerance, + max_num_steps, msgs, theta, x, x_int); + + std::vector>> + y_converted; + for (size_t i = 0; i < y.size(); ++i) + y_converted.push_back(to_array_1d(y[i])); + + return y_converted; } } // namespace math diff --git a/stan/math/rev/functor/integrate_ode_bdf.hpp b/stan/math/rev/functor/integrate_ode_bdf.hpp index 07a6e36eeb3..c3877bdb875 100644 --- a/stan/math/rev/functor/integrate_ode_bdf.hpp +++ b/stan/math/rev/functor/integrate_ode_bdf.hpp @@ -1,18 +1,22 @@ #ifndef STAN_MATH_REV_FUNCTOR_INTEGRATE_ODE_BDF_HPP #define STAN_MATH_REV_FUNCTOR_INTEGRATE_ODE_BDF_HPP -#include -#include +#include +#include +#include #include #include namespace stan { namespace math { -template ode_bdf + */ +template -std::vector>> -integrate_ode_bdf(const F& f, const std::vector& y0, const T_t0& t0, +std::vector>> +integrate_ode_bdf(const F& f, const std::vector& y0, const T_t0& t0, const std::vector& ts, const std::vector& theta, const std::vector& x, const std::vector& x_int, @@ -20,10 +24,17 @@ integrate_ode_bdf(const F& f, const std::vector& y0, const T_t0& t0, double relative_tolerance = 1e-10, double absolute_tolerance = 1e-10, long int max_num_steps = 1e8) { // NOLINT(runtime/int) - stan::math::cvodes_integrator integrator; - return integrator.integrate("integrate_ode_bdf", f, y0, t0, ts, theta, x, - x_int, msgs, relative_tolerance, - absolute_tolerance, max_num_steps); + internal::integrate_ode_std_vector_interface_adapter f_adapted(f); + auto y = ode_bdf_tol_impl("integrate_ode_bdf", f_adapted, to_vector(y0), t0, + ts, relative_tolerance, absolute_tolerance, + max_num_steps, msgs, theta, x, x_int); + + std::vector>> + y_converted; + for (size_t i = 0; i < y.size(); ++i) + y_converted.push_back(to_array_1d(y[i])); + + return y_converted; } } // namespace math diff --git a/stan/math/rev/functor/ode_adams.hpp b/stan/math/rev/functor/ode_adams.hpp new file mode 100644 index 00000000000..f02f4695ec2 --- /dev/null +++ b/stan/math/rev/functor/ode_adams.hpp @@ -0,0 +1,157 @@ +#ifndef STAN_MATH_REV_FUNCTOR_ODE_ADAMS_HPP +#define STAN_MATH_REV_FUNCTOR_ODE_ADAMS_HPP + +#include +#include +#include +#include +#include + +namespace stan { +namespace math { + +/** + * Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of + * times, { t1, t2, t3, ... } using the non-stiff Adams-Moulton solver from + * CVODES. + * + * \p f must define an operator() with the signature as: + * template + * Eigen::Matrix, Eigen::Dynamic, 1> + * operator()(const T_t& t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_Args&... args); + * + * t is the time, y is the vector-valued state, msgs is a stream for error + * messages, and args are optional arguments passed to the ODE solve function + * (which are passed through to \p f without modification). + * + * @tparam F Type of ODE right hand side + * @tparam T_0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param function_name Calling function name (for printing debugging messages) + * @param f Right hand side of the ODE + * @param y0 Initial state + * @param t0 Initial time + * @param ts Times at which to solve the ODE at. All values must be sorted and + * not less than t0. + * @param relative_tolerance Relative tolerance passed to CVODES + * @param absolute_tolerance Absolute tolerance passed to CVODES + * @param max_num_steps Upper limit on the number of integration steps to + * take between each output (error if exceeded) + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return Solution to ODE at times \p ts + */ +template +std::vector, + Eigen::Dynamic, 1>> +ode_adams_tol_impl(const char* function_name, const F& f, + const Eigen::Matrix& y0, + const T_t0& t0, const std::vector& ts, + double relative_tolerance, double absolute_tolerance, + long int max_num_steps, // NOLINT(runtime/int) + std::ostream* msgs, const T_Args&... args) { + cvodes_integrator integrator( + function_name, f, y0, t0, ts, relative_tolerance, absolute_tolerance, + max_num_steps, msgs, args...); + + return integrator(); +} + +/** + * Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of + * times, { t1, t2, t3, ... } using the non-stiff Adams-Moulton solver from + * CVODES. + * + * \p f must define an operator() with the signature as: + * template + * Eigen::Matrix, Eigen::Dynamic, 1> + * operator()(const T_t& t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_Args&... args); + * + * t is the time, y is the vector-valued state, msgs is a stream for error + * messages, and args are optional arguments passed to the ODE solve function + * (which are passed through to \p f without modification). + * + * @tparam F Type of ODE right hand side + * @tparam T_0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param f Right hand side of the ODE + * @param y0 Initial state + * @param t0 Initial time + * @param ts Times at which to solve the ODE at. All values must be sorted and + * not less than t0. + * @param relative_tolerance Relative tolerance passed to CVODES + * @param absolute_tolerance Absolute tolerance passed to CVODES + * @param max_num_steps Upper limit on the number of integration steps to + * take between each output (error if exceeded) + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return Solution to ODE at times \p ts + */ +template +std::vector, + Eigen::Dynamic, 1>> +ode_adams_tol(const F& f, const Eigen::Matrix& y0, + const T_t0& t0, const std::vector& ts, + double relative_tolerance, double absolute_tolerance, + long int max_num_steps, // NOLINT(runtime/int) + std::ostream* msgs, const T_Args&... args) { + return ode_adams_tol_impl("ode_adams_tol", f, y0, t0, ts, relative_tolerance, + absolute_tolerance, max_num_steps, msgs, args...); +} + +/** + * Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of + * times, { t1, t2, t3, ... } using the non-stiff Adams-Moulton + * solver in CVODES with defaults for relative_tolerance, absolute_tolerance, + * and max_num_steps. + * + * \p f must define an operator() with the signature as: + * template + * Eigen::Matrix, Eigen::Dynamic, 1> + * operator()(const T_t& t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_Args&... args); + * + * t is the time, y is the vector-valued state, msgs is a stream for error + * messages, and args are optional arguments passed to the ODE solve function + * (which are passed through to \p f without modification). + * + * @tparam F Type of ODE right hand side + * @tparam T_0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param f Right hand side of the ODE + * @param y0 Initial state + * @param t0 Initial time + * @param ts Times at which to solve the ODE at. All values must be sorted and + * not less than t0. + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return Solution to ODE at times \p ts + */ +template +std::vector, + Eigen::Dynamic, 1>> +ode_adams(const F& f, const Eigen::Matrix& y0, + const T_t0& t0, const std::vector& ts, std::ostream* msgs, + const T_Args&... args) { + double relative_tolerance = 1e-10; + double absolute_tolerance = 1e-10; + long int max_num_steps = 1e8; // NOLINT(runtime/int) + + return ode_adams_tol_impl("ode_adams", f, y0, t0, ts, relative_tolerance, + absolute_tolerance, max_num_steps, msgs, args...); +} + +} // namespace math +} // namespace stan +#endif diff --git a/stan/math/rev/functor/ode_bdf.hpp b/stan/math/rev/functor/ode_bdf.hpp new file mode 100644 index 00000000000..26f348352f9 --- /dev/null +++ b/stan/math/rev/functor/ode_bdf.hpp @@ -0,0 +1,157 @@ +#ifndef STAN_MATH_REV_FUNCTOR_ODE_BDF_HPP +#define STAN_MATH_REV_FUNCTOR_ODE_BDF_HPP + +#include +#include +#include +#include +#include + +namespace stan { +namespace math { + +/** + * Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of + * times, { t1, t2, t3, ... } using the stiff backward differentiation formula + * BDF solver from CVODES. + * + * \p f must define an operator() with the signature as: + * template + * Eigen::Matrix, Eigen::Dynamic, 1> + * operator()(const T_t& t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_Args&... args); + * + * t is the time, y is the vector-valued state, msgs is a stream for error + * messages, and args are optional arguments passed to the ODE solve function + * (which are passed through to \p f without modification). + * + * @tparam F Type of ODE right hand side + * @tparam T_0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param function_name Calling function name (for printing debugging messages) + * @param f Right hand side of the ODE + * @param y0 Initial state + * @param t0 Initial time + * @param ts Times at which to solve the ODE at. All values must be sorted and + * not less than t0. + * @param relative_tolerance Relative tolerance passed to CVODES + * @param absolute_tolerance Absolute tolerance passed to CVODES + * @param max_num_steps Upper limit on the number of integration steps to + * take between each output (error if exceeded) + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return Solution to ODE at times \p ts + */ +template +std::vector, + Eigen::Dynamic, 1>> +ode_bdf_tol_impl(const char* function_name, const F& f, + const Eigen::Matrix& y0, + const T_t0& t0, const std::vector& ts, + double relative_tolerance, double absolute_tolerance, + long int max_num_steps, // NOLINT(runtime/int) + std::ostream* msgs, const T_Args&... args) { + cvodes_integrator integrator( + function_name, f, y0, t0, ts, relative_tolerance, absolute_tolerance, + max_num_steps, msgs, args...); + + return integrator(); +} + +/** + * Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of + * times, { t1, t2, t3, ... } using the stiff backward differentiation formula + * BDF solver from CVODES. + * + * \p f must define an operator() with the signature as: + * template + * Eigen::Matrix, Eigen::Dynamic, 1> + * operator()(const T_t& t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_Args&... args); + * + * t is the time, y is the vector-valued state, msgs is a stream for error + * messages, and args are optional arguments passed to the ODE solve function + * (which are passed through to \p f without modification). + * + * @tparam F Type of ODE right hand side + * @tparam T_0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param f Right hand side of the ODE + * @param y0 Initial state + * @param t0 Initial time + * @param ts Times at which to solve the ODE at. All values must be sorted and + * not less than t0. + * @param relative_tolerance Relative tolerance passed to CVODES + * @param absolute_tolerance Absolute tolerance passed to CVODES + * @param max_num_steps Upper limit on the number of integration steps to + * take between each output (error if exceeded) + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return Solution to ODE at times \p ts + */ +template +std::vector, + Eigen::Dynamic, 1>> +ode_bdf_tol(const F& f, const Eigen::Matrix& y0, + const T_t0& t0, const std::vector& ts, + double relative_tolerance, double absolute_tolerance, + long int max_num_steps, // NOLINT(runtime/int) + std::ostream* msgs, const T_Args&... args) { + return ode_bdf_tol_impl("ode_bdf_tol", f, y0, t0, ts, relative_tolerance, + absolute_tolerance, max_num_steps, msgs, args...); +} + +/** + * Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of + * times, { t1, t2, t3, ... } using the stiff backward differentiation formula + * (BDF) solver in CVODES with defaults for relative_tolerance, + * absolute_tolerance, and max_num_steps. + * + * \p f must define an operator() with the signature as: + * template + * Eigen::Matrix, Eigen::Dynamic, 1> + * operator()(const T_t& t, const Eigen::Matrix& y, + * std::ostream* msgs, const T_Args&... args); + * + * t is the time, y is the vector-valued state, msgs is a stream for error + * messages, and args are optional arguments passed to the ODE solve function + * (which are passed through to \p f without modification). + * + * @tparam F Type of ODE right hand side + * @tparam T_0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param f Right hand side of the ODE + * @param y0 Initial state + * @param t0 Initial time + * @param ts Times at which to solve the ODE at. All values must be sorted and + * not less than t0. + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return Solution to ODE at times \p ts + */ +template +std::vector, + Eigen::Dynamic, 1>> +ode_bdf(const F& f, const Eigen::Matrix& y0, + const T_t0& t0, const std::vector& ts, std::ostream* msgs, + const T_Args&... args) { + double relative_tolerance = 1e-10; + double absolute_tolerance = 1e-10; + long int max_num_steps = 1e8; // NOLINT(runtime/int) + + return ode_bdf_tol_impl("ode_bdf", f, y0, t0, ts, relative_tolerance, + absolute_tolerance, max_num_steps, msgs, args...); +} + +} // namespace math +} // namespace stan +#endif diff --git a/stan/math/rev/functor/ode_store_sensitivities.hpp b/stan/math/rev/functor/ode_store_sensitivities.hpp new file mode 100644 index 00000000000..8a93ff846df --- /dev/null +++ b/stan/math/rev/functor/ode_store_sensitivities.hpp @@ -0,0 +1,110 @@ +#ifndef STAN_MATH_REV_FUNCTOR_ODE_STORE_SENSITIVITIES_HPP +#define STAN_MATH_REV_FUNCTOR_ODE_STORE_SENSITIVITIES_HPP + +#include +#include +#include +#include +#include + +namespace stan { +namespace math { + +/** + * Build output vars for a state of the ODE solve, storing the sensitivities + * precomputed using the forward sensitivity problem in precomputed varis. + * + * @tparam F Type of ODE right hand side + * @tparam T_y0_t0 Type of initial state + * @tparam T_t0 Type of initial time + * @tparam T_ts Type of output times + * @tparam T_Args Types of pass-through parameters + * + * @param f Right hand side of the ODE + * @param coupled_state Current state of the coupled_ode_system + * @param y0 Initial state + * @param t0 Initial time + * @param t Times at which to solve the ODE at + * @param[in, out] msgs the print stream for warning messages + * @param args Extra arguments passed unmodified through to ODE right hand side + * @return ODE state with scalar type var + */ +template ...>* = nullptr> +Eigen::Matrix ode_store_sensitivities( + const F& f, const std::vector& coupled_state, + const Eigen::Matrix& y0, const T_t0& t0, + const T_t& t, std::ostream* msgs, const Args&... args) { + const size_t N = y0.size(); + const size_t num_y0_vars = count_vars(y0); + const size_t num_args_vars = count_vars(args...); + const size_t num_t0_vars = count_vars(t0); + const size_t num_t_vars = count_vars(t); + Eigen::Matrix yt(N); + + Eigen::VectorXd y(N); + for (size_t n = 0; n < N; ++n) { + y.coeffRef(n) = coupled_state[n]; + } + + Eigen::VectorXd f_y_t; + if (is_var::value) + f_y_t = f(value_of(t), y, msgs, eval(value_of(args))...); + + Eigen::VectorXd f_y0_t0; + if (is_var::value) + f_y0_t0 + = f(value_of(t0), eval(value_of(y0)), msgs, eval(value_of(args))...); + + const size_t total_vars + = num_y0_vars + num_args_vars + num_t0_vars + num_t_vars; + + vari** varis + = ChainableStack::instance_->memalloc_.alloc_array(total_vars); + + save_varis(varis, y0, args..., t0, t); + + // memory for a column major jacobian + double* jacobian_mem + = ChainableStack::instance_->memalloc_.alloc_array(N + * total_vars); + + Eigen::Map jacobian(jacobian_mem, total_vars, N); + + for (size_t j = 0; j < N; ++j) { + for (size_t k = 0; k < num_y0_vars; ++k) { + jacobian.coeffRef(k, j) = coupled_state[N + num_y0_vars * k + j]; + } + + for (size_t k = 0; k < num_args_vars; ++k) { + jacobian.coeffRef(num_y0_vars + k, j) + = coupled_state[N + N * num_y0_vars + N * k + j]; + } + + if (is_var::value) { + double dyt_dt0 = 0.0; + for (size_t k = 0; k < num_y0_vars; ++k) { + dyt_dt0 -= f_y0_t0.coeffRef(k) * coupled_state[N + num_y0_vars * k + j]; + } + jacobian.coeffRef(num_y0_vars + num_args_vars, j) = dyt_dt0; + } + + if (is_var::value) { + jacobian.coeffRef(num_y0_vars + num_args_vars + num_t0_vars, j) + = f_y_t.coeffRef(j); + } + + // jacobian_mem + j * total_vars points to the jth column of jacobian + yt(j) = new precomputed_gradients_vari(y(j), total_vars, varis, + jacobian_mem + j * total_vars); + } + + return yt; +} + +} // namespace math +} // namespace stan + +#endif diff --git a/test/unit/math/prim/err/check_finite_test.cpp b/test/unit/math/prim/err/check_finite_test.cpp index 3312d2202ba..46752d8c4b7 100644 --- a/test/unit/math/prim/err/check_finite_test.cpp +++ b/test/unit/math/prim/err/check_finite_test.cpp @@ -49,6 +49,12 @@ TEST(ErrorHandlingMat, CheckFinite_Matrix) { ASSERT_NO_THROW(check_finite(function, "x", x)) << "check_finite should be true with finite x"; + ASSERT_NO_THROW(check_finite(function, "x", x.array())) + << "check_finite should be true with finite x"; + + ASSERT_NO_THROW(check_finite(function, "x", x.transpose())) + << "check_finite should be true with finite x"; + x.resize(3); x << -1, 0, std::numeric_limits::infinity(); EXPECT_THROW(check_finite(function, "x", x), std::domain_error) diff --git a/test/unit/math/prim/err/check_ordered_test.cpp b/test/unit/math/prim/err/check_ordered_test.cpp index f23d8740ac5..03fc94d5419 100644 --- a/test/unit/math/prim/err/check_ordered_test.cpp +++ b/test/unit/math/prim/err/check_ordered_test.cpp @@ -25,6 +25,9 @@ TEST(ErrorHandling, checkOrdered) { y = {0, std::numeric_limits::infinity(), std::numeric_limits::infinity()}; EXPECT_THROW(check_ordered("check_ordered", "y", y), std::domain_error); + + y = {-1, 3, 2}; + EXPECT_THROW(check_ordered("check_ordered", "y", y), std::domain_error); } TEST(ErrorHandling, checkOrdered_one_indexed_message) { diff --git a/test/unit/math/prim/err/check_sorted_test.cpp b/test/unit/math/prim/err/check_sorted_test.cpp new file mode 100644 index 00000000000..41d1450fd69 --- /dev/null +++ b/test/unit/math/prim/err/check_sorted_test.cpp @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include + +using stan::math::check_sorted; + +TEST(ErrorHandling, checkSorted) { + std::vector y = {0, 1, 2}; + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y = {0, 1, std::numeric_limits::infinity()}; + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y = {-10, 1, std::numeric_limits::infinity()}; + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y = {-std::numeric_limits::infinity(), 1, + std::numeric_limits::infinity()}; + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y = {0, 0, 0}; + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y = {0, std::numeric_limits::infinity(), + std::numeric_limits::infinity()}; + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y = {-1, 3, 2}; + EXPECT_THROW(check_sorted("check_sorted", "y", y), std::domain_error); +} + +TEST(ErrorHandling, checkSorted_one_indexed_message) { + std::string message; + std::vector y = {0, 5, 1}; + + try { + check_sorted("check_sorted", "y", y); + FAIL() << "should have thrown"; + } catch (std::domain_error& e) { + message = e.what(); + } catch (...) { + FAIL() << "threw the wrong error"; + } + + EXPECT_NE(std::string::npos, message.find("element at 3")) << message; +} + +TEST(ErrorHandling, checkSorted_nan) { + double nan = std::numeric_limits::quiet_NaN(); + std::vector y = {0, 1, 2}; + + for (size_t i = 0; i < y.size(); i++) { + y[i] = nan; + EXPECT_THROW(check_sorted("check_sorted", "y", y), std::domain_error); + y[i] = i; + } + for (size_t i = 0; i < y.size(); i++) { + y = {0.0, 10.0, std::numeric_limits::infinity()}; + y[i] = nan; + EXPECT_THROW(check_sorted("check_sorted", "y", y), std::domain_error); + } +} + +TEST(ErrorHandlingMatrix, checkSorted) { + Eigen::Matrix y; + y.resize(3); + + y << 0, 1, 2; + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y << 0, 10, std::numeric_limits::infinity(); + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y << -10, 10, std::numeric_limits::infinity(); + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y << -std::numeric_limits::infinity(), 10, + std::numeric_limits::infinity(); + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y << 0, 0, 0; + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y << 0, std::numeric_limits::infinity(), + std::numeric_limits::infinity(); + EXPECT_NO_THROW(check_sorted("check_sorted", "y", y)); + + y << -1, 3, 2; + EXPECT_THROW(check_sorted("check_sorted", "y", y), std::domain_error); +} + +TEST(ErrorHandlingMatrix, checkSorted_one_indexed_message) { + std::string message; + Eigen::Matrix y; + y.resize(3); + + y << 0, 5, 1; + try { + check_sorted("check_sorted", "y", y); + FAIL() << "should have thrown"; + } catch (std::domain_error& e) { + message = e.what(); + } catch (...) { + FAIL() << "threw the wrong error"; + } + + EXPECT_NE(std::string::npos, message.find("element at 3")) << message; +} + +TEST(ErrorHandlingMatrix, checkSorted_nan) { + Eigen::Matrix y; + double nan = std::numeric_limits::quiet_NaN(); + y.resize(3); + + y << 0, 1, 2; + for (int i = 0; i < y.size(); i++) { + y[i] = nan; + EXPECT_THROW(check_sorted("check_sorted", "y", y), std::domain_error); + y[i] = i; + } + for (int i = 0; i < y.size(); i++) { + y << 0, 10, std::numeric_limits::infinity(); + y[i] = nan; + EXPECT_THROW(check_sorted("check_sorted", "y", y), std::domain_error); + } +} diff --git a/test/unit/math/prim/fun/eval_test.cpp b/test/unit/math/prim/fun/eval_test.cpp new file mode 100644 index 00000000000..ca40918f056 --- /dev/null +++ b/test/unit/math/prim/fun/eval_test.cpp @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include +#include +#include + +TEST(MathFunctions, eval) { + using stan::math::eval; + double x = 5.0; + EXPECT_FLOAT_EQ(5.0, eval(x)); + EXPECT_FLOAT_EQ(5.0, eval(5)); +} + +TEST(MathMatrixPrimArr, eval) { + using stan::math::eval; + using std::vector; + + vector a; + for (size_t i = 0; i < 10; ++i) + a.push_back(i + 1); + + vector b; + for (size_t i = 10; i < 15; ++i) + b.push_back(i + 1); + + EXPECT_STD_VECTOR_FLOAT_EQ(a, eval(a)); + EXPECT_STD_VECTOR_FLOAT_EQ(b, eval(b)); +} + +TEST(MathFunctions, eval_int_return_type_short_circuit) { + std::vector a(5, 0); + const std::vector b(5, 0); + EXPECT_TRUE( + (std::is_same&>::value)); + EXPECT_TRUE((std::is_same&>::value)); +} + +TEST(MathFunctions, eval_double_return_type_short_circuit) { + std::vector a(5, 0); + const std::vector b(5, 0); + EXPECT_TRUE((std::is_same&>::value)); + EXPECT_TRUE((std::is_same&>::value)); +} + +TEST(MathMatrixPrimMat, eval) { + using stan::math::eval; + + Eigen::Matrix a; + for (int i = 0; i < 2; i++) + for (int j = 0; j < 5; j++) + a(i, j) = i * 5 + j; + Eigen::Matrix b; + for (int i = 0; i < 5; i++) + for (int j = 0; j < 1; j++) + b(i, j) = 10 + i * 5 + j; + + EXPECT_MATRIX_FLOAT_EQ(a, eval(a)); + EXPECT_MATRIX_FLOAT_EQ(b, eval(b)); +} + +TEST(MathFunctions, eval_vector_of_vectors) { + std::vector a(5, 0); + const std::vector b(5, 0); + std::vector> va(5, a); + const std::vector> vb(5, b); + EXPECT_TRUE((std::is_same>&>::value)); + EXPECT_TRUE((std::is_same>&>::value)); + + auto vva = stan::math::eval(va); + auto vvb = stan::math::eval(va); + + for (size_t i = 0; i < va.size(); ++i) { + for (size_t j = 0; j < va[i].size(); ++j) { + EXPECT_FLOAT_EQ(vva[i][j], a[j]); + } + } + + for (size_t i = 0; i < vb.size(); ++i) { + for (size_t j = 0; j < vb[i].size(); ++j) { + EXPECT_FLOAT_EQ(vvb[i][j], b[j]); + } + } +} + +TEST(MathFunctions, eval_vector_of_eigen) { + Eigen::VectorXd a = Eigen::VectorXd::Random(5); + Eigen::RowVectorXd b = Eigen::RowVectorXd::Random(5); + Eigen::MatrixXd c = Eigen::MatrixXd::Random(5, 5); + std::vector va(5, a); + std::vector vb(5, b); + std::vector vc(5, c); + EXPECT_TRUE((std::is_same&>::value)); + EXPECT_TRUE((std::is_same&>::value)); + EXPECT_TRUE((std::is_same&>::value)); + + auto vva = stan::math::eval(va); + auto vvb = stan::math::eval(vb); + auto vvc = stan::math::eval(vc); + + for (size_t i = 0; i < vva.size(); ++i) + EXPECT_MATRIX_FLOAT_EQ(vva[i], a); + + for (size_t i = 0; i < vvb.size(); ++i) + EXPECT_MATRIX_FLOAT_EQ(vvb[i], b); + + for (size_t i = 0; i < vvc.size(); ++i) + EXPECT_MATRIX_FLOAT_EQ(vvc[i], c); +} + +TEST(MathMatrixPrimMat, eval_expression) { + using stan::math::eval; + + Eigen::MatrixXd a = Eigen::MatrixXd::Random(5, 4); + Eigen::MatrixXd res_a = eval(2 * a); + Eigen::MatrixXd correct_a = 2 * a; + EXPECT_MATRIX_EQ(correct_a, res_a); + + Eigen::VectorXi b = Eigen::VectorXi::Random(7); + Eigen::VectorXi res_b = eval(2 * b); + Eigen::VectorXi correct_b = 2 * b; + EXPECT_TYPED_MATRIX_EQ(correct_b, res_b, int); + + Eigen::ArrayXXd c = a.array(); + Eigen::ArrayXXd res_c = eval(2 * c); + Eigen::ArrayXXd correct_c = 2 * c; + for (int i = 0; i < res_c.size(); i++) + EXPECT_EQ(correct_c(i), res_c(i)); +} + +TEST(MathFunctions, eval_return_type_short_circuit_std_vector) { + std::vector a(5); + const std::vector b(5); + EXPECT_TRUE((std::is_same&>::value)); + EXPECT_TRUE((std::is_same&>::value)); +} + +TEST(MathFunctions, eval_return_type_short_circuit_vector_xd) { + Eigen::Matrix a(5); + const Eigen::Matrix b(5); + EXPECT_TRUE((std::is_same&>::value)); + EXPECT_TRUE( + (std::is_same&>::value)); +} + +TEST(MathFunctions, eval_return_type_short_circuit_row_vector_xd) { + Eigen::Matrix a(5); + const Eigen::Matrix b(5); + EXPECT_TRUE((std::is_same&>::value)); + EXPECT_TRUE( + (std::is_same&>::value)); +} + +TEST(MathFunctions, eval_return_type_short_circuit_matrix_xd) { + Eigen::Matrix a(5, 4); + const Eigen::Matrix b(5, 4); + EXPECT_TRUE((std::is_same< + decltype(stan::math::eval(a)), + Eigen::Matrix&>::value)); + EXPECT_TRUE((std::is_same&>::value)); +} + +TEST(MathFunctions, eval_return_type_expression) { + const Eigen::Matrix a(5, 4); + Eigen::Matrix b(4, 4); + + const auto& expr_a = 3 * a; + auto expr_b = b * b; + + EXPECT_TRUE( + (std::is_same< + decltype(stan::math::eval(expr_a)), + const Eigen::Matrix>::value)); + EXPECT_TRUE( + (std::is_same< + decltype(stan::math::eval(expr_b)), + const Eigen::Matrix>::value)); +} + +TEST(MathFunctions, eval_return_type_short_circuit_static_sized_matrix) { + Eigen::Matrix a; + const Eigen::Matrix b; + EXPECT_TRUE((std::is_same&>::value)); + EXPECT_TRUE((std::is_same&>::value)); +} diff --git a/test/unit/math/prim/fun/value_of_test.cpp b/test/unit/math/prim/fun/value_of_test.cpp index a9881261d4a..ba8e0c08798 100644 --- a/test/unit/math/prim/fun/value_of_test.cpp +++ b/test/unit/math/prim/fun/value_of_test.cpp @@ -75,6 +75,60 @@ TEST(MathMatrixPrimMat, value_of) { EXPECT_MATRIX_FLOAT_EQ(b, d_b); } +TEST(MathFunctions, value_of_vector_of_vectors) { + std::vector a(5, 0); + const std::vector b(5, 0); + std::vector> va(5, a); + const std::vector> vb(5, b); + EXPECT_TRUE((std::is_same>&>::value)); + EXPECT_TRUE((std::is_same>&>::value)); + + auto vva = stan::math::value_of(va); + auto vvb = stan::math::value_of(va); + + for (size_t i = 0; i < va.size(); ++i) { + for (size_t j = 0; j < va[i].size(); ++j) { + EXPECT_FLOAT_EQ(vva[i][j], a[j]); + } + } + + for (size_t i = 0; i < vb.size(); ++i) { + for (size_t j = 0; j < vb[i].size(); ++j) { + EXPECT_FLOAT_EQ(vvb[i][j], b[j]); + } + } +} + +TEST(MathFunctions, value_of_vector_of_eigen) { + Eigen::VectorXd a = Eigen::VectorXd::Random(5); + Eigen::RowVectorXd b = Eigen::RowVectorXd::Random(5); + Eigen::MatrixXd c = Eigen::MatrixXd::Random(5, 5); + std::vector va(5, a); + std::vector vb(5, b); + std::vector vc(5, c); + EXPECT_TRUE((std::is_same&>::value)); + EXPECT_TRUE((std::is_same&>::value)); + EXPECT_TRUE((std::is_same&>::value)); + + auto vva = stan::math::value_of(va); + auto vvb = stan::math::value_of(vb); + auto vvc = stan::math::value_of(vc); + + for (size_t i = 0; i < vva.size(); ++i) + EXPECT_MATRIX_FLOAT_EQ(vva[i], a); + + for (size_t i = 0; i < vvb.size(); ++i) + EXPECT_MATRIX_FLOAT_EQ(vvb[i], b); + + for (size_t i = 0; i < vvc.size(); ++i) + EXPECT_MATRIX_FLOAT_EQ(vvc[i], c); +} + TEST(MathMatrixPrimMat, value_of_expression) { using stan::math::value_of; diff --git a/test/unit/math/prim/functor/coupled_ode_observer_test.cpp b/test/unit/math/prim/functor/coupled_ode_observer_test.cpp deleted file mode 100644 index 8a5cd9b2562..00000000000 --- a/test/unit/math/prim/functor/coupled_ode_observer_test.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -struct StanMathCoupledOdeObserver : public ::testing::Test { - std::stringstream msgs; - std::vector x; - std::vector x_int; -}; - -TEST_F(StanMathCoupledOdeObserver, observe_states_dddd) { - using stan::math::coupled_ode_system; - - harm_osc_ode_fun harm_osc; - - std::vector y0(2); - std::vector theta(1); - - y0[0] = 1.0; - y0[1] = 0.5; - theta[0] = 0.15; - - stan::math::coupled_ode_system - coupled_system(harm_osc, y0, theta, x, x_int, &msgs); - - std::vector> y; - double t0 = 0; - int T = 10; - std::vector ts(T); - for (int t = 0; t < T; t++) - ts[t] = t; - - stan::math::coupled_ode_observer - observer(harm_osc, y0, theta, t0, ts, x, x_int, &msgs, y); - - int k = 0; - std::vector> ys_coupled(T); - for (int t = 0; t < T; t++) { - std::vector coupled_state(coupled_system.size(), 0.0); - for (int n = 0; n < coupled_system.size(); n++) - coupled_state[n] = ++k; - ys_coupled[t] = coupled_state; - observer(coupled_state, ts[t]); - } - - EXPECT_EQ(T, y.size()); - for (int t = 0; t < T; t++) - EXPECT_EQ(2, y[t].size()); - - for (int t = 0; t < T; t++) - for (int n = 0; n < 2; n++) - EXPECT_FLOAT_EQ(ys_coupled[t][n], y[t][n]) - << "(" << n << ", " << t << "): " - << "for (double, double) the coupled system is the base system"; - - // calling it once too often will fire an exception as the observer - // runs out of time states. - // note: the EXPECT_THROW_MSG calls the expression given twice which - // is why the time-state number is 11 by the time the comparison is - // done with the expected string (I would have expected 10 instead - // of 11). - std::string message - = "coupled_ode_observer: time-state number is 10, but must be less than " - "10"; - std::vector ys_coupled_0(coupled_system.size(), 0.0); - EXPECT_THROW_MSG(observer(ys_coupled_0, 20.), std::logic_error, message); -} diff --git a/test/unit/math/prim/functor/coupled_ode_system_test.cpp b/test/unit/math/prim/functor/coupled_ode_system_test.cpp index ed4369e3a72..679d2b33b7e 100644 --- a/test/unit/math/prim/functor/coupled_ode_system_test.cpp +++ b/test/unit/math/prim/functor/coupled_ode_system_test.cpp @@ -20,20 +20,21 @@ TEST_F(StanMathCoupledOdeSystem, initial_state_dd) { const int N = 3; const int M = 4; - std::vector y0_d(N, 0.0); - std::vector theta_d(M, 0.0); + Eigen::VectorXd y0_d(N); + std::vector theta_d(M); for (int n = 0; n < N; n++) - y0_d[n] = n + 1; + y0_d(n) = n + 1; for (int m = 0; m < M; m++) theta_d[m] = 10 * (m + 1); - coupled_ode_system coupled_system_dd( - base_ode, y0_d, theta_d, x, x_int, &msgs); + coupled_ode_system, + std::vector, std::vector> + coupled_system_dd(base_ode, y0_d, &msgs, theta_d, x, x_int); std::vector state = coupled_system_dd.initial_state(); for (int n = 0; n < N; n++) - EXPECT_FLOAT_EQ(y0_d[n], state[n]) + EXPECT_FLOAT_EQ(y0_d(n), state[n]) << "we don't need derivatives of y0; " << "initial state gets the initial values"; for (size_t n = N; n < state.size(); n++) @@ -47,11 +48,11 @@ TEST_F(StanMathCoupledOdeSystem, size) { const int N = 3; const int M = 4; - std::vector y0_d(N, 0.0); + Eigen::VectorXd y0_d(N); std::vector theta_d(M, 0.0); - coupled_ode_system coupled_system_dd( - base_ode, y0_d, theta_d, x, x_int, &msgs); + coupled_ode_system + coupled_system_dd(base_ode, y0_d, &msgs, 1, 1.0, y0_d); EXPECT_EQ(N, coupled_system_dd.size()); } @@ -65,15 +66,15 @@ TEST_F(StanMathCoupledOdeSystem, recover_exception) { mock_throwing_ode_functor throwing_ode(message); - std::vector y0_d(N, 0.0); - std::vector theta_v(M, 0.0); + Eigen::VectorXd y0_d(N); + std::vector theta_v(M); coupled_ode_system, double, - double> - coupled_system_dd(throwing_ode, y0_d, theta_v, x, x_int, &msgs); + std::vector, std::vector, std::vector> + coupled_system_dd(throwing_ode, y0_d, &msgs, theta_v, x, x_int); - std::vector y(3, 0); - std::vector dy_dt(3, 0); + std::vector y(3); + std::vector dy_dt(3); double t = 10; diff --git a/test/unit/math/prim/functor/harmonic_oscillator.hpp b/test/unit/math/prim/functor/harmonic_oscillator.hpp index fc5a6bae3f7..6c409e36496 100644 --- a/test/unit/math/prim/functor/harmonic_oscillator.hpp +++ b/test/unit/math/prim/functor/harmonic_oscillator.hpp @@ -28,6 +28,25 @@ struct harm_osc_ode_fun { } }; +struct harm_osc_ode_fun_eigen { + template + inline auto operator()(const T0& t_in, + const Eigen::Matrix& y_in, + std::ostream* msgs, const std::vector& theta, + const std::vector& x, + const std::vector& x_int) const { + if (y_in.size() != 2) + throw std::domain_error( + "this function was called with inconsistent state"); + + Eigen::Matrix, Eigen::Dynamic, 1> res(2); + res(0) = y_in(1); + res(1) = -y_in(0) - theta[0] * y_in(1); + + return res; + } +}; + struct harm_osc_ode_data_fun { template inline std::vector> diff --git a/test/unit/math/prim/functor/integrate_ode_rk45_test.cpp b/test/unit/math/prim/functor/integrate_ode_rk45_prim_test.cpp similarity index 57% rename from test/unit/math/prim/functor/integrate_ode_rk45_test.cpp rename to test/unit/math/prim/functor/integrate_ode_rk45_prim_test.cpp index 8f308675ea0..6d665abd7ec 100644 --- a/test/unit/math/prim/functor/integrate_ode_rk45_test.cpp +++ b/test/unit/math/prim/functor/integrate_ode_rk45_prim_test.cpp @@ -9,21 +9,20 @@ #include #include -template +std::ostream* msgs = nullptr; + +template void sho_death_test(F harm_osc, std::vector& y0, double t0, - std::vector& ts, std::vector& theta, - std::vector& x, std::vector& x_int) { - EXPECT_DEATH( - stan::math::integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int), - ""); + std::vector& ts, Args&... args) { + EXPECT_DEATH(stan::math::integrate_ode_rk45(harm_osc, y0, t0, ts, args...), + ""); } -template +template void sho_value_test(F harm_osc, std::vector& y0, double t0, - std::vector& ts, std::vector& theta, - std::vector& x, std::vector& x_int) { - std::vector > ode_res_vd - = stan::math::integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int); + std::vector& ts, Args&... args) { + std::vector> ode_res_vd + = stan::math::integrate_ode_rk45(harm_osc, y0, t0, ts, args...); EXPECT_NEAR(0.995029, ode_res_vd[0][0], 1e-5); EXPECT_NEAR(-0.0990884, ode_res_vd[0][1], 1e-5); @@ -101,55 +100,55 @@ TEST(StanMathOde_integrate_ode_rk45, error_conditions) { std::vector x(3, 1); std::vector x_int(2, 0); - ASSERT_NO_THROW(integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int)); + ASSERT_NO_THROW((integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int))); std::vector y0_bad; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int)), std::invalid_argument, "initial state has size 0"); double t0_bad = 2.0; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int)), std::domain_error, "initial time is 2, but must be less than 0.1"); std::vector ts_bad; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int)), std::invalid_argument, "times has size 0"); ts_bad.push_back(3); ts_bad.push_back(1); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int), - std::domain_error, "times is not a valid ordered vector"); + (integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int)), + std::domain_error, "times is not a valid sorted vector"); std::vector theta_bad; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int)), std::out_of_range, "vector"); std::vector x_bad; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int)), std::out_of_range, "vector"); std::vector x_int_bad; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int_bad), + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int_bad)), std::out_of_range, "vector"); - EXPECT_THROW_MSG(integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int, 0, - -1, 1e-6, 10), - std::invalid_argument, "relative_tolerance"); + EXPECT_THROW_MSG((integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int, 0, + -1, 1e-6, 10)), + std::domain_error, "relative_tolerance"); - EXPECT_THROW_MSG(integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int, 0, - 1e-6, -1, 10), - std::invalid_argument, "absolute_tolerance"); + EXPECT_THROW_MSG((integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int, 0, + 1e-6, -1, 10)), + std::domain_error, "absolute_tolerance"); - EXPECT_THROW_MSG(integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int, 0, - 1e-6, 1e-6, -1), - std::invalid_argument, "max_num_steps"); + EXPECT_THROW_MSG((integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int, 0, + 1e-6, 1e-6, -1)), + std::domain_error, "max_num_steps"); } TEST(StanMathOde_integrate_ode_rk45, error_conditions_nan) { @@ -172,7 +171,7 @@ TEST(StanMathOde_integrate_ode_rk45, error_conditions_nan) { std::vector x(3, 1); std::vector x_int(2, 0); - ASSERT_NO_THROW(integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int)); + ASSERT_NO_THROW((integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int))); double nan = std::numeric_limits::quiet_NaN(); std::stringstream expected_is_nan; @@ -181,46 +180,46 @@ TEST(StanMathOde_integrate_ode_rk45, error_conditions_nan) { std::vector y0_bad = y0; y0_bad[0] = nan; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int)), std::domain_error, "initial state"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int)), std::domain_error, expected_is_nan.str()); double t0_bad = nan; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int)), std::domain_error, "initial time"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int)), std::domain_error, expected_is_nan.str()); std::vector ts_bad = ts; ts_bad[0] = nan; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int)), std::domain_error, "times"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int)), std::domain_error, expected_is_nan.str()); std::vector theta_bad = theta; theta_bad[0] = nan; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int), - std::domain_error, "parameter vector"); + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int)), + std::domain_error, "parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int)), std::domain_error, expected_is_nan.str()); if (x.size() > 0) { std::vector x_bad = x; x_bad[0] = nan; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int), - std::domain_error, "continuous data"); + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int)), + std::domain_error, "parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int)), std::domain_error, expected_is_nan.str()); } } @@ -249,88 +248,110 @@ TEST(StanMathOde_integrate_ode_rk45, error_conditions_inf) { std::vector x(3, 1); std::vector x_int(2, 0); - ASSERT_NO_THROW(integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int)); + ASSERT_NO_THROW((integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x, x_int))); double inf = std::numeric_limits::infinity(); std::vector y0_bad = y0; y0_bad[0] = inf; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int)), std::domain_error, "initial state"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int)), std::domain_error, expected_is_inf.str()); y0_bad[0] = -inf; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int)), std::domain_error, "initial state"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int)), std::domain_error, expected_is_neg_inf.str()); double t0_bad = inf; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int)), std::domain_error, "initial time"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int)), std::domain_error, expected_is_inf.str()); t0_bad = -inf; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int)), std::domain_error, "initial time"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0_bad, ts, theta, x, x_int)), std::domain_error, expected_is_neg_inf.str()); std::vector ts_bad = ts; ts_bad.push_back(inf); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int)), std::domain_error, "times"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int)), std::domain_error, expected_is_inf.str()); ts_bad[0] = -inf; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int)), std::domain_error, "times"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts_bad, theta, x, x_int)), std::domain_error, expected_is_neg_inf.str()); std::vector theta_bad = theta; theta_bad[0] = inf; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int), - std::domain_error, "parameter vector"); + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int)), + std::domain_error, "parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int)), std::domain_error, expected_is_inf.str()); theta_bad[0] = -inf; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int), - std::domain_error, "parameter vector"); + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int)), + std::domain_error, "parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta_bad, x, x_int)), std::domain_error, expected_is_neg_inf.str()); if (x.size() > 0) { std::vector x_bad = x; x_bad[0] = inf; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int), - std::domain_error, "continuous data"); + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int)), + std::domain_error, "parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int)), std::domain_error, expected_is_inf.str()); x_bad[0] = -inf; EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int), - std::domain_error, "continuous data"); + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int)), + std::domain_error, "parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int), + (integrate_ode_rk45(harm_osc, y0, t0, ts, theta, x_bad, x_int)), std::domain_error, expected_is_neg_inf.str()); } } + +TEST(StanMathOde_integrate_ode_rk45, error_name) { + using stan::math::integrate_ode_rk45; + harm_osc_ode_data_fun harm_osc; + + std::vector theta; + theta.push_back(0.15); + + double t0 = 0; + + std::vector ts; + for (int i = 0; i < 100; i++) + ts.push_back(t0 + 0.1 * (i + 1)); + + std::vector x(3, 1); + std::vector x_int(2, 0); + + std::vector y0_bad; + EXPECT_THROW_MSG( + (integrate_ode_rk45(harm_osc, y0_bad, t0, ts, theta, x, x_int)), + std::invalid_argument, "integrate_ode_rk45"); +} diff --git a/test/unit/math/prim/functor/integrate_ode_std_vector_interface_adapter_test.cpp b/test/unit/math/prim/functor/integrate_ode_std_vector_interface_adapter_test.cpp new file mode 100644 index 00000000000..1d7f484a356 --- /dev/null +++ b/test/unit/math/prim/functor/integrate_ode_std_vector_interface_adapter_test.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include + +TEST(StanMath, check_values) { + harm_osc_ode_data_fun harm_osc; + stan::math::internal::integrate_ode_std_vector_interface_adapter< + harm_osc_ode_data_fun> + harm_osc_adapted(harm_osc); + + std::vector theta = {0.15}; + std::vector y = {1.0, 0.5}; + + std::vector x(3, 1); + std::vector x_int(2, 0); + + double t = 1.0; + + Eigen::VectorXd out1 + = stan::math::to_vector(harm_osc(t, y, theta, x, x_int, nullptr)); + Eigen::VectorXd out2 + = harm_osc_adapted(t, stan::math::to_vector(y), nullptr, theta, x, x_int); + + EXPECT_MATRIX_FLOAT_EQ(out1, out2); +} diff --git a/test/unit/math/prim/functor/mock_ode_functor.hpp b/test/unit/math/prim/functor/mock_ode_functor.hpp index 504dbfe14d9..e24320a54b0 100644 --- a/test/unit/math/prim/functor/mock_ode_functor.hpp +++ b/test/unit/math/prim/functor/mock_ode_functor.hpp @@ -3,14 +3,12 @@ #include struct mock_ode_functor { - template - inline std::vector> operator()( - const T0& t_in, const std::vector& y_in, const std::vector& theta, - const std::vector& x, const std::vector& x_int, - std::ostream* msgs) const { - std::vector> states; - states = y_in; - return states; + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t_in, const Eigen::Matrix& y_in, + std::ostream* msgs, const T2& a1, const T3& a2, + const T4& a3) const { + return y_in.template cast>(); } }; #endif diff --git a/test/unit/math/prim/functor/mock_throwing_ode_functor.hpp b/test/unit/math/prim/functor/mock_throwing_ode_functor.hpp index ef014678d3e..1ed5686dfdc 100644 --- a/test/unit/math/prim/functor/mock_throwing_ode_functor.hpp +++ b/test/unit/math/prim/functor/mock_throwing_ode_functor.hpp @@ -19,10 +19,11 @@ struct mock_throwing_ode_functor { } template - inline std::vector> operator()( - const T0& t_in, const std::vector& y_in, const std::vector& theta, - const std::vector& x, const std::vector& x_int, - std::ostream* msgs) const { + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t_in, const Eigen::Matrix& y_in, + std::ostream* msgs, const std::vector& theta, + const std::vector& x, + const std::vector& x_int) const { mock_throwing_ode_functor_count++; if (N_ == mock_throwing_ode_functor_count) throw E(msg_); diff --git a/test/unit/math/prim/functor/ode_rk45_prim_test.cpp b/test/unit/math/prim/functor/ode_rk45_prim_test.cpp new file mode 100644 index 00000000000..848b8aad112 --- /dev/null +++ b/test/unit/math/prim/functor/ode_rk45_prim_test.cpp @@ -0,0 +1,310 @@ +#include +#include +#include +#include +#include + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +struct CosArgWrongSize { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(2); + out << stan::math::cos(sum_(vec) * t), 0; + return out; + } +}; + +TEST(ode_rk45_prim, y0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + Eigen::VectorXd y0inf(1); + Eigen::VectorXd y0NaN(1); + Eigen::VectorXd y0_empty; + y0NaN << stan::math::NOT_A_NUMBER; + y0inf << stan::math::INFTY; + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0inf, t0, ts, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0NaN, t0, ts, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0_empty, t0, ts, nullptr, a), + std::invalid_argument); +} + +TEST(ode_rk45_prim, t0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + double t0inf = stan::math::INFTY; + double t0NaN = stan::math::NOT_A_NUMBER; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0inf, ts, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0NaN, ts, nullptr, a), + std::domain_error); +} + +TEST(ode_rk45_prim, ts_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + std::vector ts_repeat = {0.45, 0.45}; + std::vector ts_lots = {0.45, 0.45, 1.1, 1.1, 2.0}; + std::vector ts_empty = {}; + std::vector ts_early = {-0.45, 0.2}; + std::vector ts_decreasing = {0.45, 0.2}; + std::vector tsinf = {stan::math::INFTY, 1.1}; + std::vector tsNaN = {0.45, stan::math::NOT_A_NUMBER}; + + double a = 1.5; + + std::vector out; + EXPECT_NO_THROW(out + = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a)); + EXPECT_EQ(out.size(), ts.size()); + + EXPECT_NO_THROW( + out = stan::math::ode_rk45(CosArg1(), y0, t0, ts_repeat, nullptr, a)); + EXPECT_EQ(out.size(), ts_repeat.size()); + EXPECT_MATRIX_FLOAT_EQ(out[0], out[1]); + + EXPECT_NO_THROW( + out = stan::math::ode_rk45(CosArg1(), y0, t0, ts_lots, nullptr, a)); + EXPECT_EQ(out.size(), ts_lots.size()); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts_empty, nullptr, a), + std::invalid_argument); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts_early, nullptr, a), + std::domain_error); + + EXPECT_THROW( + stan::math::ode_rk45(CosArg1(), y0, t0, ts_decreasing, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, tsinf, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, tsNaN, nullptr, a), + std::domain_error); +} + +TEST(ode_rk45_prim, one_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, va)); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, ea)); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, vva)); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, vea)); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, veaNaN), + std::domain_error); +} + +TEST(ode_rk45_prim, two_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, a)); + + EXPECT_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, va)); + + EXPECT_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, ea)); + + EXPECT_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, vva)); + + EXPECT_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, vea)); + + EXPECT_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a, veaNaN), + std::domain_error); +} + +TEST(ode_rk45_prim, rhs_wrong_size_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45(CosArgWrongSize(), y0, t0, ts, nullptr, a), + std::invalid_argument); +} + +TEST(ode_rk45_prim, error_name) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double ainf = stan::math::INFTY; + + EXPECT_THROW_MSG(stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, ainf), + std::domain_error, "ode_rk45"); +} diff --git a/test/unit/math/prim/functor/ode_rk45_tol_prim_test.cpp b/test/unit/math/prim/functor/ode_rk45_tol_prim_test.cpp new file mode 100644 index 00000000000..4d9a52cc718 --- /dev/null +++ b/test/unit/math/prim/functor/ode_rk45_tol_prim_test.cpp @@ -0,0 +1,448 @@ +#include +#include +#include +#include +#include + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +struct CosArgWrongSize { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(2); + out << stan::math::cos(sum_(vec) * t), 0; + return out; + } +}; + +TEST(ode_rk45_tol_prim, y0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + Eigen::VectorXd y0inf(1); + Eigen::VectorXd y0NaN(1); + Eigen::VectorXd y0_empty; + y0NaN << stan::math::NOT_A_NUMBER; + y0inf << stan::math::INFTY; + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0inf, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0NaN, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0_empty, t0, ts, 1e-10, + 1e-10, 1e6, nullptr, a), + std::invalid_argument); +} + +TEST(ode_rk45_tol_prim, t0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + double t0inf = stan::math::INFTY; + double t0NaN = stan::math::NOT_A_NUMBER; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0inf, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0NaN, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_rk45_tol_prim, ts_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + std::vector ts_repeat = {0.45, 0.45}; + std::vector ts_lots = {0.45, 0.45, 1.1, 1.1, 2.0}; + std::vector ts_empty = {}; + std::vector ts_early = {-0.45, 0.2}; + std::vector ts_decreasing = {0.45, 0.2}; + std::vector tsinf = {stan::math::INFTY, 1.1}; + std::vector tsNaN = {0.45, stan::math::NOT_A_NUMBER}; + + double a = 1.5; + + std::vector out; + EXPECT_NO_THROW(out = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, + 1e-10, 1e6, nullptr, a)); + EXPECT_EQ(out.size(), ts.size()); + + EXPECT_NO_THROW(out + = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts_repeat, + 1e-10, 1e-10, 1e6, nullptr, a)); + EXPECT_EQ(out.size(), ts_repeat.size()); + EXPECT_MATRIX_FLOAT_EQ(out[0], out[1]); + + EXPECT_NO_THROW(out + = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts_lots, 1e-10, + 1e-10, 1e6, nullptr, a)); + EXPECT_EQ(out.size(), ts_lots.size()); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts_empty, 1e-10, + 1e-10, 1e6, nullptr, a), + std::invalid_argument); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts_early, 1e-10, + 1e-10, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts_decreasing, 1e-10, + 1e-10, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, tsinf, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, tsNaN, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_rk45_tol_prim, one_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, va)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, ea)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vva)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vea)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, veaNaN), + std::domain_error); +} + +TEST(ode_rk45_tol_prim, two_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, a)); + + EXPECT_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, va)); + + EXPECT_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, ea)); + + EXPECT_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vva)); + + EXPECT_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vea)); + + EXPECT_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, veaNaN), + std::domain_error); +} + +TEST(ode_rk45_tol_prim, rtol_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double rtol = 1e-6; + double rtol_negative = -1e-6; + double rtolinf = stan::math::INFTY; + double rtolNaN = stan::math::NOT_A_NUMBER; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, rtol, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, rtol_negative, + 1e-10, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, rtolinf, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, rtolNaN, 1e-10, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_rk45_tol_prim, atol_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double atol = 1e-6; + double atol_negative = -1e-6; + double atolinf = stan::math::INFTY; + double atolNaN = stan::math::NOT_A_NUMBER; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, atol, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, + atol_negative, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, atolinf, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, atolNaN, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_rk45_tol_prim, max_num_steps_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + int max_num_steps = 500; + int max_num_steps_negative = -500; + int max_num_steps_zero = 0; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + max_num_steps, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + max_num_steps_negative, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + max_num_steps_zero, nullptr, a), + std::domain_error); +} + +TEST(ode_rk45_tol_prim, rhs_wrong_size_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, a)); + + EXPECT_THROW(stan::math::ode_rk45_tol(CosArgWrongSize(), y0, t0, ts, 1e-6, + 1e-6, 100, nullptr, a), + std::invalid_argument); +} + +TEST(ode_rk45_tol_prim, error_name) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double ainf = stan::math::INFTY; + + EXPECT_THROW_MSG(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, ainf), + std::domain_error, "ode_rk45_tol"); +} + +TEST(ode_rk45_tol_prim, too_much_work) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1e10}; + + double a = 1.0; + + EXPECT_THROW_MSG(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, a), + std::domain_error, + "ode_rk45_tol: Failed to integrate to next output time"); +} diff --git a/test/unit/math/prim/functor/ode_store_sensitivities_test.cpp b/test/unit/math/prim/functor/ode_store_sensitivities_test.cpp new file mode 100644 index 00000000000..acef36e6884 --- /dev/null +++ b/test/unit/math/prim/functor/ode_store_sensitivities_test.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include +#include + +TEST(MathPrim, ode_store_sensitivities) { + mock_ode_functor base_ode; + + size_t N = 5; + + int ignored1 = 0; + double ignored2 = 0.0; + Eigen::VectorXd ignored3; + std::vector> ignored4; + + std::vector coupled_state(N); + for (size_t i = 0; i < coupled_state.size(); ++i) + coupled_state[i] = i + 1; + + Eigen::VectorXd output = stan::math::ode_store_sensitivities( + base_ode, coupled_state, Eigen::VectorXd::Ones(3).eval(), 0, 0.0, nullptr, + ignored1, ignored2, ignored3, ignored4); + + EXPECT_EQ(output.size(), coupled_state.size()); + EXPECT_MATRIX_FLOAT_EQ(output, stan::math::to_vector(coupled_state).eval()); +} diff --git a/test/unit/math/rev/core/zero_adjoints_test.cpp b/test/unit/math/rev/core/zero_adjoints_test.cpp new file mode 100644 index 00000000000..07ab008e47b --- /dev/null +++ b/test/unit/math/rev/core/zero_adjoints_test.cpp @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include + +TEST(AgradRev, zero_arithmetic) { + int a = 1.0; + double b = 2; + std::vector va(5, a); + std::vector vb(5, b); + Eigen::VectorXd c = Eigen::VectorXd::Random(5); + Eigen::RowVectorXd d = Eigen::RowVectorXd::Random(5); + Eigen::MatrixXd e = Eigen::MatrixXd::Random(5, 5); + std::vector> vva(5, va); + std::vector> vvb(5, vb); + std::vector vc(5, c); + std::vector vd(5, d); + std::vector ve(5, e); + + stan::math::zero_adjoints(a); + stan::math::zero_adjoints(b); + stan::math::zero_adjoints(va); + stan::math::zero_adjoints(vb); + stan::math::zero_adjoints(c); + stan::math::zero_adjoints(d); + stan::math::zero_adjoints(e); + stan::math::zero_adjoints(vva); + stan::math::zero_adjoints(vvb); + stan::math::zero_adjoints(vc); + stan::math::zero_adjoints(vd); + stan::math::zero_adjoints(ve); + + stan::math::zero_adjoints(a, b, va, vb, c, d, e, vva, vvb, vc, vd, ve); +} + +TEST(AgradRev, zero_var) { + using stan::math::var; + using stan::math::vari; + + var a(5.0); + a.vi_->adj_ = 2.0; + + stan::math::zero_adjoints(a); + EXPECT_FLOAT_EQ(a.vi_->adj_, 0.0); + + stan::math::recover_memory(); +} + +TEST(AgradRev, zero_std_vector_var) { + using stan::math::var; + using stan::math::vari; + + std::vector a(5, 0.0); + for (size_t i = 0; i < a.size(); ++i) + a[i].vi_->adj_ = i + 1.0; + + stan::math::zero_adjoints(a); + for (size_t i = 0; i < a.size(); ++i) + EXPECT_FLOAT_EQ(a[i].vi_->adj_, 0.0); + + stan::math::recover_memory(); +} + +TEST(AgradRev, zero_vector_var) { + using stan::math::var; + using stan::math::vari; + + Eigen::Matrix a + = Eigen::VectorXd::Zero(5).template cast(); + for (size_t i = 0; i < a.size(); ++i) + a(i).vi_->adj_ = i + 1.0; + + stan::math::zero_adjoints(a); + for (size_t i = 0; i < a.size(); ++i) + EXPECT_FLOAT_EQ(a(i).vi_->adj_, 0.0); + + stan::math::recover_memory(); +} + +TEST(AgradRev, zero_row_vector_var) { + using stan::math::var; + using stan::math::vari; + + Eigen::Matrix a + = Eigen::RowVectorXd::Zero(5).template cast(); + for (size_t i = 0; i < a.size(); ++i) + a(i).vi_->adj_ = i + 1.0; + + stan::math::zero_adjoints(a); + for (size_t i = 0; i < a.size(); ++i) + EXPECT_FLOAT_EQ(a(i).vi_->adj_, 0.0); + + stan::math::recover_memory(); +} + +TEST(AgradRev, zero_matrix_var) { + using stan::math::var; + using stan::math::vari; + + Eigen::Matrix a + = Eigen::MatrixXd::Zero(5, 5).template cast(); + for (size_t i = 0; i < a.size(); ++i) + a(i).vi_->adj_ = i + 1.0; + + stan::math::zero_adjoints(a); + for (size_t i = 0; i < a.size(); ++i) + EXPECT_FLOAT_EQ(a(i).vi_->adj_, 0.0); + + stan::math::recover_memory(); +} + +TEST(AgradRev, zero_std_vector_std_vector_var) { + using stan::math::var; + using stan::math::vari; + + std::vector a(5, 0.0); + std::vector b(5, 1.0); + std::vector c(5, 2.0); + std::vector> va = {a, b, c}; + for (size_t i = 0; i < va.size(); ++i) + for (size_t j = 0; j < va[i].size(); ++j) + va[i][j].vi_->adj_ = i + 1.0; + + stan::math::zero_adjoints(va); + for (size_t i = 0; i < va.size(); ++i) + for (size_t j = 0; j < va[i].size(); ++j) + EXPECT_FLOAT_EQ(va[i][j].vi_->adj_, 0.0); + + stan::math::recover_memory(); +} + +TEST(AgradRev, zero_std_vector_vector_var) { + using stan::math::var; + using stan::math::vari; + + Eigen::Matrix a + = Eigen::VectorXd::Zero(3).template cast(); + Eigen::Matrix b + = Eigen::VectorXd::Zero(4).template cast(); + Eigen::Matrix c + = Eigen::VectorXd::Zero(5).template cast(); + std::vector> va = {a, b, c}; + for (size_t i = 0; i < va.size(); ++i) + for (size_t j = 0; j < va[i].size(); ++j) + va[i](j).vi_->adj_ = i + 1.0; + + stan::math::zero_adjoints(va); + for (size_t i = 0; i < va.size(); ++i) + for (size_t j = 0; j < va[i].size(); ++j) + EXPECT_FLOAT_EQ(va[i](j).vi_->adj_, 0.0); + + stan::math::recover_memory(); +} + +TEST(AgradRev, zero_std_vector_row_vector_var) { + using stan::math::var; + using stan::math::vari; + + Eigen::Matrix a + = Eigen::RowVectorXd::Zero(3).template cast(); + Eigen::Matrix b + = Eigen::RowVectorXd::Zero(4).template cast(); + Eigen::Matrix c + = Eigen::RowVectorXd::Zero(5).template cast(); + std::vector> va = {a, b, c}; + for (size_t i = 0; i < va.size(); ++i) + for (size_t j = 0; j < va[i].size(); ++j) + va[i](j).vi_->adj_ = i + 1.0; + + stan::math::zero_adjoints(va); + for (size_t i = 0; i < va.size(); ++i) + for (size_t j = 0; j < va[i].size(); ++j) + EXPECT_FLOAT_EQ(va[i](j).vi_->adj_, 0.0); + + stan::math::recover_memory(); +} + +TEST(AgradRev, zero_std_vector_matrix_var) { + using stan::math::var; + using stan::math::vari; + + Eigen::Matrix a + = Eigen::MatrixXd::Zero(3, 4).template cast(); + Eigen::Matrix b + = Eigen::MatrixXd::Zero(4, 5).template cast(); + Eigen::Matrix c + = Eigen::MatrixXd::Zero(5, 6).template cast(); + std::vector> va + = {a, b, c}; + for (size_t i = 0; i < va.size(); ++i) + for (size_t j = 0; j < va[i].size(); ++j) + va[i](j).vi_->adj_ = i + 1.0; + + stan::math::zero_adjoints(va); + for (size_t i = 0; i < va.size(); ++i) + for (size_t j = 0; j < va[i].size(); ++j) + EXPECT_FLOAT_EQ(va[i](j).vi_->adj_, 0.0); + + stan::math::recover_memory(); +} + +TEST(AgradRev, zero_multi) { + using stan::math::var; + using stan::math::vari; + + int a = 2; + double b = 3; + var c(5.0); + c.vi_->adj_ = 2.0; + std::vector d(5, 1.0); + for (size_t i = 0; i < d.size(); ++i) + d[i].vi_->adj_ = i + 1.0; + std::vector e(5, 1); + std::vector f(5, 1.0); + + stan::math::zero_adjoints(a, b, c, d, e, f); + EXPECT_FLOAT_EQ(c.vi_->adj_, 0.0); + for (size_t i = 0; i < d.size(); ++i) + EXPECT_FLOAT_EQ(d[i].vi_->adj_, 0.0); + + stan::math::recover_memory(); +} diff --git a/test/unit/math/rev/fun/value_of_test.cpp b/test/unit/math/rev/fun/value_of_test.cpp index 2f1f51951ae..672c207ad70 100644 --- a/test/unit/math/rev/fun/value_of_test.cpp +++ b/test/unit/math/rev/fun/value_of_test.cpp @@ -87,6 +87,68 @@ TEST(AgradMatrix, value_of) { } } +TEST(AgradMatrix, value_of_vector_of_vectors) { + using stan::math::var; + std::vector a(5, 0); + const std::vector b(5, 0); + std::vector> va(5, a); + const std::vector> vb(5, b); + EXPECT_TRUE((std::is_same>>::value)); + EXPECT_TRUE((std::is_same>>::value)); + + auto vva = stan::math::value_of(va); + auto vvb = stan::math::value_of(va); + + for (size_t i = 0; i < va.size(); ++i) { + for (size_t j = 0; j < va[i].size(); ++j) { + EXPECT_FLOAT_EQ(vva[i][j], a[j].val()); + } + } + + for (size_t i = 0; i < vb.size(); ++i) { + for (size_t j = 0; j < vb[i].size(); ++j) { + EXPECT_FLOAT_EQ(vvb[i][j], b[j].val()); + } + } +} + +TEST(AgradMatrix, value_of_vector_of_eigen) { + using stan::math::var; + Eigen::Matrix a + = Eigen::VectorXd::Random(5).template cast(); + Eigen::Matrix b + = Eigen::RowVectorXd::Random(5).template cast(); + Eigen::Matrix c + = Eigen::MatrixXd::Random(5, 5).template cast(); + std::vector> va(5, a); + std::vector> vb(5, b); + std::vector> vc(5, c); + EXPECT_TRUE((std::is_same>::value)); + EXPECT_TRUE((std::is_same>::value)); + EXPECT_TRUE((std::is_same>::value)); + + auto vva = stan::math::value_of(va); + auto vvb = stan::math::value_of(vb); + auto vvc = stan::math::value_of(vc); + + for (size_t i = 0; i < vva.size(); ++i) + for (size_t j = 0; j < vva[i].size(); ++j) + EXPECT_FLOAT_EQ(vva[i](j), a(j).val()); + + for (size_t i = 0; i < vvb.size(); ++i) + for (size_t j = 0; j < vva[i].size(); ++j) + EXPECT_FLOAT_EQ(vvb[i](j), b(j).val()); + + for (size_t i = 0; i < vvc.size(); ++i) + for (size_t j = 0; j < vva[i].size(); ++j) + EXPECT_FLOAT_EQ(vvc[i](j), c(j).val()); +} + TEST(AgradMatrix, value_of_expression) { using Eigen::Matrix; using Eigen::MatrixXd; diff --git a/test/unit/math/rev/functor/coupled_ode_observer_test.cpp b/test/unit/math/rev/functor/coupled_ode_observer_test.cpp deleted file mode 100644 index 26b29c78560..00000000000 --- a/test/unit/math/rev/functor/coupled_ode_observer_test.cpp +++ /dev/null @@ -1,371 +0,0 @@ -// mat is needed to get operands_and_partials working with -// std::vector edges needed in coupled_ode_observer -#include -#include -#include -#include -#include -#include -#include - -struct StanRevOde : public ::testing::Test { - void TearDown() { stan::math::recover_memory(); } - std::stringstream msgs; - std::vector x; - std::vector x_int; -}; - -TEST_F(StanRevOde, observe_states_dvdd) { - using stan::math::coupled_ode_system; - using stan::math::var; - - mock_ode_functor mock_ode; - - std::vector y0(2); - y0[0] = 1.0; - y0[1] = 0.5; - - std::vector theta(1); - theta[0] = 0.15; - - coupled_ode_system coupled_system( - mock_ode, y0, theta, x, x_int, &msgs); - - std::vector> y; - double t0 = 0; - int T = 10; - std::vector ts(T); - for (int t = 0; t < T; t++) - ts[t] = t; - - stan::math::coupled_ode_observer - observer(mock_ode, y0, theta, t0, ts, x, x_int, &msgs, y); - - size_t k = 0; - std::vector> ys_coupled(T); - for (size_t t = 0; t < T; t++) { - std::vector coupled_state(coupled_system.size(), 0.0); - for (size_t n = 0; n < coupled_system.size(); n++) - coupled_state[n] = ++k; - ys_coupled[t] = coupled_state; - observer(coupled_state, ts[0]); - } - - EXPECT_EQ(T, y.size()); - for (size_t t = 0; t < T; t++) - EXPECT_EQ(2U, y[t].size()); - - for (size_t t = 0; t < T; t++) { - for (size_t n = 0; n < 2; n++) - EXPECT_FLOAT_EQ(ys_coupled[t][n], y[t][n].val()); - for (size_t n = 0; n < 2; n++) { - y[t][n].grad(); - EXPECT_FLOAT_EQ(ys_coupled[t][2 + n], theta[0].adj()); - stan::math::set_zero_all_adjoints(); - } - } -} - -TEST_F(StanRevOde, observe_states_vddd) { - using stan::math::coupled_ode_system; - using stan::math::var; - - mock_ode_functor mock_ode; - - std::vector y0(2); - std::vector theta(1); - - y0[0] = 1.0; - y0[1] = 0.5; - theta[0] = 0.15; - - coupled_ode_system coupled_system( - mock_ode, y0, theta, x, x_int, &msgs); - - std::vector> y; - double t0 = 0; - int T = 10; - std::vector ts(T); - for (int t = 0; t < T; t++) - ts[t] = t; - - stan::math::coupled_ode_observer - observer(mock_ode, y0, theta, t0, ts, x, x_int, &msgs, y); - - size_t k = 0; - std::vector> ys_coupled(T); - for (size_t t = 0; t < T; t++) { - std::vector coupled_state(coupled_system.size(), 0.0); - for (size_t n = 0; n < coupled_system.size(); n++) - coupled_state[n] = ++k; - ys_coupled[t] = coupled_state; - observer(coupled_state, ts[t]); - } - - EXPECT_EQ(T, y.size()); - for (size_t t = 0; t < T; t++) - EXPECT_EQ(2U, y[t].size()); - - for (size_t t = 0; t < T; t++) { - for (size_t n = 0; n < 2; n++) { - EXPECT_FLOAT_EQ(ys_coupled[t][n], y[t][n].val()); - } - for (size_t n = 0; n < 2; n++) { - y[t][n].grad(); - EXPECT_FLOAT_EQ(ys_coupled[t][2 + n], y0[0].adj()); - EXPECT_FLOAT_EQ(ys_coupled[t][2 + 2 + n], y0[1].adj()); - stan::math::set_zero_all_adjoints(); - } - } -} - -TEST_F(StanRevOde, observe_states_vvdd) { - using stan::math::coupled_ode_system; - using stan::math::var; - - harm_osc_ode_fun harm_osc; - - std::vector y0(2); - std::vector theta(1); - - y0[0] = 1.0; - y0[1] = 0.5; - theta[0] = 0.15; - - coupled_ode_system coupled_system( - harm_osc, y0, theta, x, x_int, &msgs); - - std::vector> y; - double t0 = 0; - int T = 10; - std::vector ts(T); - for (int t = 0; t < T; t++) - ts[t] = t; - - stan::math::coupled_ode_observer - observer(harm_osc, y0, theta, t0, ts, x, x_int, &msgs, y); - - size_t k = 0; - std::vector> ys_coupled(T); - for (size_t t = 0; t < T; t++) { - std::vector coupled_state(coupled_system.size(), 0.0); - for (size_t n = 0; n < coupled_system.size(); n++) - coupled_state[n] = ++k; - ys_coupled[t] = coupled_state; - observer(coupled_state, ts[t]); - } - - EXPECT_EQ(T, y.size()); - for (size_t t = 0; t < T; t++) - EXPECT_EQ(2U, y[t].size()); - - for (size_t t = 0; t < T; t++) { - for (size_t n = 0; n < 2; n++) - EXPECT_FLOAT_EQ(ys_coupled[t][n], y[t][n].val()); - - for (size_t n = 0; n < 2; n++) { - y[t][n].grad(); - EXPECT_FLOAT_EQ(ys_coupled[t][2 + n], y0[0].adj()); - EXPECT_FLOAT_EQ(ys_coupled[t][2 + 2 + n], y0[1].adj()); - EXPECT_FLOAT_EQ(ys_coupled[t][2 + 2 * 2 + n], theta[0].adj()); - stan::math::set_zero_all_adjoints(); - } - } -} - -TEST_F(StanRevOde, observe_states_ddvd) { - using stan::math::coupled_ode_system; - using stan::math::var; - - mock_ode_functor mock_ode; - - std::vector y0(2); - y0[0] = 1.0; - y0[1] = 0.5; - - std::vector theta(1); - theta[0] = 0.15; - - coupled_ode_system coupled_system( - mock_ode, y0, theta, x, x_int, &msgs); - - std::vector> y; - var t0 = 0; - int T = 10; - std::vector ts(T); - for (int t = 0; t < T; t++) - ts[t] = t; - - stan::math::coupled_ode_observer - observer(mock_ode, y0, theta, t0, ts, x, x_int, &msgs, y); - - size_t k = 0; - std::vector> ys_coupled(T); - for (size_t t = 0; t < T; t++) { - std::vector coupled_state(coupled_system.size(), 0.0); - for (size_t n = 0; n < coupled_system.size(); n++) - coupled_state[n] = ++k; - ys_coupled[t] = coupled_state; - observer(coupled_state, ts[0]); - } - - EXPECT_EQ(T, y.size()); - for (size_t t = 0; t < T; t++) - EXPECT_EQ(2U, y[t].size()); - - for (size_t t = 0; t < T; t++) { - for (size_t n = 0; n < 2; n++) - EXPECT_FLOAT_EQ(ys_coupled[t][n], y[t][n].val()); - for (size_t n = 0; n < 2; n++) { - y[t][n].grad(); - EXPECT_FLOAT_EQ(-y0[n], t0.adj()); - stan::math::set_zero_all_adjoints(); - } - } -} - -TEST_F(StanRevOde, observe_states_dddv) { - using stan::math::coupled_ode_system; - using stan::math::var; - - harm_osc_ode_fun harm_osc; - - std::vector y0(2); - std::vector theta(1); - - y0[0] = 1.0; - y0[1] = 0.5; - theta[0] = 0.15; - - coupled_ode_system coupled_system( - harm_osc, y0, theta, x, x_int, &msgs); - - std::vector> y; - double t0 = 0; - int T = 10; - std::vector ts(T); - for (int t = 0; t < T; t++) - ts[t] = t; - - stan::math::coupled_ode_observer - observer(harm_osc, y0, theta, t0, ts, x, x_int, &msgs, y); - - size_t k = 0; - std::vector> ys_coupled(T); - for (size_t t = 0; t < T; t++) { - std::vector coupled_state(coupled_system.size(), 0.0); - for (size_t n = 0; n < coupled_system.size(); n++) - coupled_state[n] = ++k; - ys_coupled[t] = coupled_state; - observer(coupled_state, value_of(ts[t])); - } - - EXPECT_EQ(T, y.size()); - for (size_t t = 0; t < T; t++) - EXPECT_EQ(2U, y[t].size()); - - for (size_t t = 0; t < T; t++) { - for (size_t n = 0; n < 2; n++) - EXPECT_FLOAT_EQ(ys_coupled[t][n], y[t][n].val()); - - std::vector yt(2); - yt[0] = ys_coupled[t][0]; - yt[1] = ys_coupled[t][1]; - std::vector dy_dt - = harm_osc(value_of(ts[t]), yt, theta, x, x_int, &msgs); - for (size_t n = 0; n < 2; n++) { - y[t][n].grad(); - EXPECT_FLOAT_EQ(ts[t].adj(), dy_dt[n]); - stan::math::set_zero_all_adjoints(); - } - } - - // trigger wrong size error by starting with a too large initial - // state which will cause the observer to trigger a size mismatch - y0.push_back(3); - stan::math::coupled_ode_observer - throwing_observer(harm_osc, y0, theta, t0, ts, x, x_int, &msgs, y); - - // this one will throw - std::string message = "this function was called with inconsistent state"; - EXPECT_THROW_MSG( - (throwing_observer(std::vector(coupled_system.size(), 0.0), 0)), - std::logic_error, message); -} - -TEST_F(StanRevOde, observe_states_ddvd_error) { - using stan::math::coupled_ode_system; - using stan::math::var; - - harm_osc_ode_wrong_size_1_fun harm_osc1; - harm_osc_ode_fun harm_osc2; - - std::vector y0(2); - std::vector theta(1); - - y0[0] = 1.0; - y0[1] = 0.5; - theta[0] = 0.15; - - std::vector> y; - var t0v = 0; - int T = 10; - std::vector tsd(T); - for (int t = 0; t < T; t++) { - tsd[t] = t; - } - - EXPECT_THROW_MSG( - (stan::math::coupled_ode_observer( - harm_osc1, y0, theta, t0v, tsd, x, x_int, &msgs, y)), - std::invalid_argument, - std::string( - "coupled_ode_observer: dy_dt (3) and states (2) must match in size")); - - EXPECT_NO_THROW((stan::math::coupled_ode_observer( - harm_osc2, y0, theta, t0v, tsd, x, x_int, &msgs, y))); -} - -TEST_F(StanRevOde, observe_states_dddv_error) { - using stan::math::coupled_ode_system; - using stan::math::var; - - harm_osc_ode_wrong_size_1_fun harm_osc1; - harm_osc_ode_fun harm_osc2; - - std::vector y0(2); - std::vector theta(1); - - y0[0] = 1.0; - y0[1] = 0.5; - theta[0] = 0.15; - - std::vector> y; - double t0d = 0; - int T = 10; - std::vector tsv(T); - for (int t = 0; t < T; t++) { - tsv[t] = t; - } - - stan::math::coupled_ode_observer - throwing_observer(harm_osc1, y0, theta, t0d, tsv, x, x_int, &msgs, y); - - stan::math::coupled_ode_observer - observer(harm_osc2, y0, theta, t0d, tsv, x, x_int, &msgs, y); - - EXPECT_THROW_MSG( - (throwing_observer(std::vector(2), 0)), std::invalid_argument, - std::string( - "coupled_ode_observer: dy_dt (3) and states (2) must match in size")); - EXPECT_NO_THROW((observer(std::vector(2), 0))); -} diff --git a/test/unit/math/rev/functor/coupled_ode_system_test.cpp b/test/unit/math/rev/functor/coupled_ode_system_test.cpp index a097aee047e..e513a20a3c3 100644 --- a/test/unit/math/rev/functor/coupled_ode_system_test.cpp +++ b/test/unit/math/rev/functor/coupled_ode_system_test.cpp @@ -21,11 +21,11 @@ TEST_F(StanAgradRevOde, coupled_ode_system_dv) { // Run nested autodiff in this scope stan::math::nested_rev_autodiff nested; - harm_osc_ode_fun harm_osc; + harm_osc_ode_fun_eigen harm_osc; std::vector theta; std::vector z0; - std::vector y0; + Eigen::VectorXd y0(2); double t0; const size_t z_size = 4; std::vector dz_dt(z_size, 0); @@ -34,8 +34,7 @@ TEST_F(StanAgradRevOde, coupled_ode_system_dv) { t0 = 0; theta.push_back(gamma); - y0.push_back(1.0); - y0.push_back(0.5); + y0 << 1.0, 0.5; z0.push_back(1.0); z0.push_back(0.5); @@ -44,8 +43,9 @@ TEST_F(StanAgradRevOde, coupled_ode_system_dv) { std::size_t stack_size = stan::math::nested_size(); - coupled_ode_system system( - harm_osc, y0, theta, x, x_int, &msgs); + coupled_ode_system, + std::vector, std::vector> + system(harm_osc, y0, &msgs, theta, x, x_int); EXPECT_EQ(stack_size, stan::math::nested_size()) << "expecting no new things on the stack"; @@ -57,6 +57,7 @@ TEST_F(StanAgradRevOde, coupled_ode_system_dv) { EXPECT_FLOAT_EQ(2, dz_dt[2]); EXPECT_FLOAT_EQ(-1.8, dz_dt[3]); } + TEST_F(StanAgradRevOde, initial_state_dv) { using stan::math::coupled_ode_system; using stan::math::var; @@ -66,7 +67,7 @@ TEST_F(StanAgradRevOde, initial_state_dv) { const size_t M = 4; const size_t z_size = N + N * M; - std::vector y0_d(N, 0.0); + Eigen::VectorXd y0_d = Eigen::VectorXd::Zero(N); std::vector theta_v(M, 0.0); for (size_t n = 0; n < N; n++) @@ -74,8 +75,9 @@ TEST_F(StanAgradRevOde, initial_state_dv) { for (size_t m = 0; m < M; m++) theta_v[m] = 10 * (m + 1); - coupled_ode_system coupled_system_dv( - base_ode, y0_d, theta_v, x, x_int, &msgs); + coupled_ode_system, + std::vector, std::vector> + coupled_system_dv(base_ode, y0_d, &msgs, theta_v, x, x_int); std::vector state = coupled_system_dv.initial_state(); for (size_t n = 0; n < N; n++) @@ -85,6 +87,7 @@ TEST_F(StanAgradRevOde, initial_state_dv) { for (size_t n = N; n < state.size(); n++) EXPECT_FLOAT_EQ(0.0, state[n]); } + TEST_F(StanAgradRevOde, size_dv) { using stan::math::coupled_ode_system; using stan::math::var; @@ -94,11 +97,12 @@ TEST_F(StanAgradRevOde, size_dv) { const size_t M = 4; const size_t z_size = N + N * M; - std::vector y0_d(N, 0.0); + Eigen::VectorXd y0_d = Eigen::VectorXd::Zero(N); std::vector theta_v(M, 0.0); - coupled_ode_system coupled_system_dv( - base_ode, y0_d, theta_v, x, x_int, &msgs); + coupled_ode_system, + std::vector, std::vector> + coupled_system_dv(base_ode, y0_d, &msgs, theta_v, x, x_int); EXPECT_EQ(z_size, coupled_system_dv.size()); } @@ -112,11 +116,12 @@ TEST_F(StanAgradRevOde, memory_recovery_dv) { const size_t M = 4; const size_t z_size = N + N * M; - std::vector y0_d(N, 0.0); + Eigen::VectorXd y0_d = Eigen::VectorXd::Zero(N); std::vector theta_v(M, 0.0); - coupled_ode_system coupled_system_dv( - base_ode, y0_d, theta_v, x, x_int, &msgs); + coupled_ode_system, + std::vector, std::vector> + coupled_system_dv(base_ode, y0_d, &msgs, theta_v, x, x_int); std::vector z(z_size, 0); std::vector dz_dt(z_size, 0); @@ -142,11 +147,13 @@ TEST_F(StanAgradRevOde, memory_recovery_exception_dv) { SCOPED_TRACE(scoped_message.str()); mock_throwing_ode_functor throwing_ode(message, 1); - std::vector y0_d(N, 0.0); + Eigen::VectorXd y0_d = Eigen::VectorXd::Zero(N); std::vector theta_v(M, 0.0); - coupled_ode_system, double, var> - coupled_system_dv(throwing_ode, y0_d, theta_v, x, x_int, &msgs); + coupled_ode_system, std::vector, + std::vector> + coupled_system_dv(throwing_ode, y0_d, &msgs, theta_v, x, x_int); std::vector z(z_size, 0); std::vector dz_dt(z_size, 0); @@ -166,11 +173,11 @@ TEST_F(StanAgradRevOde, coupled_ode_system_vd) { // Run nested autodiff in this scope stan::math::nested_rev_autodiff nested; - harm_osc_ode_fun harm_osc; + harm_osc_ode_fun_eigen harm_osc; std::vector theta; std::vector z0; - std::vector y0_var; + Eigen::Matrix y0_var(2); std::vector y0_adj; double t0; const size_t N = 2; @@ -189,13 +196,13 @@ TEST_F(StanAgradRevOde, coupled_ode_system_vd) { z0.push_back(0.0); z0.push_back(1.0); - y0_var.push_back(1.0); - y0_var.push_back(0.5); + y0_var << 1.0, 0.5; std::size_t stack_size = stan::math::nested_size(); - coupled_ode_system system( - harm_osc, y0_var, theta, x, x_int, &msgs); + coupled_ode_system, + std::vector, std::vector> + system(harm_osc, y0_var, &msgs, theta, x, x_int); EXPECT_EQ(stack_size, stan::math::nested_size()) << "expecting no new things on the stack"; @@ -218,7 +225,8 @@ TEST_F(StanAgradRevOde, initial_state_vd) { const size_t N = 3; const size_t M = 4; - std::vector y0_v(N, 0.0); + Eigen::Matrix y0_v + = Eigen::VectorXd::Zero(N).template cast(); std::vector theta_d(M, 0.0); for (size_t n = 0; n < N; n++) @@ -226,8 +234,9 @@ TEST_F(StanAgradRevOde, initial_state_vd) { for (size_t m = 0; m < M; m++) theta_d[m] = 10 * (m + 1); - coupled_ode_system coupled_system_vd( - base_ode, y0_v, theta_d, x, x_int, &msgs); + coupled_ode_system, + std::vector, std::vector> + coupled_system_vd(base_ode, y0_v, &msgs, theta_d, x, x_int); std::vector state; @@ -238,6 +247,7 @@ TEST_F(StanAgradRevOde, initial_state_vd) { for (size_t j = 0; j < N; j++) EXPECT_FLOAT_EQ(i == j ? 1.0 : 0.0, state[N + i + j * N]); } + TEST_F(StanAgradRevOde, size_vd) { using stan::math::coupled_ode_system; using stan::math::var; @@ -247,11 +257,13 @@ TEST_F(StanAgradRevOde, size_vd) { const size_t M = 4; const size_t z_size = N + N * N; - std::vector y0_v(N, 0.0); + Eigen::Matrix y0_v + = Eigen::VectorXd::Zero(N).template cast(); std::vector theta_d(M, 0.0); - coupled_ode_system coupled_system_vd( - base_ode, y0_v, theta_d, x, x_int, &msgs); + coupled_ode_system, + std::vector, std::vector> + coupled_system_vd(base_ode, y0_v, &msgs, theta_d, x, x_int); EXPECT_EQ(z_size, coupled_system_vd.size()); } @@ -265,11 +277,13 @@ TEST_F(StanAgradRevOde, memory_recovery_vd) { const size_t M = 4; const size_t z_size = N + N * N; - std::vector y0_v(N, 0.0); + Eigen::Matrix y0_v + = Eigen::VectorXd::Zero(N).template cast(); std::vector theta_d(M, 0.0); - coupled_ode_system coupled_system_vd( - base_ode, y0_v, theta_d, x, x_int, &msgs); + coupled_ode_system, + std::vector, std::vector> + coupled_system_vd(base_ode, y0_v, &msgs, theta_d, x, x_int); std::vector z(z_size, 0); std::vector dz_dt(z_size, 0); @@ -295,11 +309,14 @@ TEST_F(StanAgradRevOde, memory_recovery_exception_vd) { SCOPED_TRACE(scoped_message.str()); mock_throwing_ode_functor throwing_ode(message, 1); - std::vector y0_v(N, 0.0); + Eigen::Matrix y0_v + = Eigen::VectorXd::Zero(N).template cast(); std::vector theta_d(M, 0.0); - coupled_ode_system, var, double> - coupled_system_vd(throwing_ode, y0_v, theta_d, x, x_int, &msgs); + coupled_ode_system, std::vector, + std::vector> + coupled_system_vd(throwing_ode, y0_v, &msgs, theta_d, x, x_int); std::vector z(z_size, 0); std::vector dz_dt(z_size, 0); @@ -323,19 +340,20 @@ TEST_F(StanAgradRevOde, coupled_ode_system_vv) { const size_t M = 1; const size_t z_size = N + N * N + N * M; - std::vector y0_var; - y0_var.push_back(1.0); - y0_var.push_back(0.5); + Eigen::Matrix y0_var(2); + y0_var << 1.0, 0.5; - std::vector theta_var; - theta_var.push_back(0.15); + std::vector theta_var(1); + theta_var[0] = 0.15; - harm_osc_ode_fun harm_osc; + harm_osc_ode_fun_eigen harm_osc; std::size_t stack_size = stan::math::nested_size(); - coupled_ode_system system( - harm_osc, y0_var, theta_var, x, x_int, &msgs); + coupled_ode_system, std::vector, + std::vector> + system(harm_osc, y0_var, &msgs, theta_var, x, x_int); EXPECT_EQ(stack_size, stan::math::nested_size()) << "expecting no new things on the stack"; @@ -352,15 +370,14 @@ TEST_F(StanAgradRevOde, coupled_ode_system_vv) { std::vector dz_dt(z_size); system(z0, dz_dt, t0); - std::vector y0_double(2); - y0_double[0] = 1.0; - y0_double[1] = 0.5; + Eigen::VectorXd y0_double(2); + y0_double << 1.0, 0.5; std::vector theta_double(1); theta_double[0] = 0.15; - std::vector dy_dt_base - = harm_osc(0.0, y0_double, theta_double, x, x_int, &msgs); + Eigen::VectorXd dy_dt_base + = harm_osc(0.0, y0_double, &msgs, theta_double, x, x_int); EXPECT_FLOAT_EQ(dy_dt_base[0], dz_dt[0]); EXPECT_FLOAT_EQ(dy_dt_base[1], dz_dt[1]); @@ -371,6 +388,7 @@ TEST_F(StanAgradRevOde, coupled_ode_system_vv) { EXPECT_FLOAT_EQ(0, dz_dt[6]); EXPECT_FLOAT_EQ(-0.5, dz_dt[7]); } + TEST_F(StanAgradRevOde, initial_state_vv) { using stan::math::coupled_ode_system; using stan::math::var; @@ -379,7 +397,8 @@ TEST_F(StanAgradRevOde, initial_state_vv) { const size_t N = 3; const size_t M = 4; - std::vector y0_v(N, 0.0); + Eigen::Matrix y0_v + = Eigen::VectorXd::Zero(N).template cast(); std::vector theta_v(M, 0.0); for (size_t n = 0; n < N; n++) @@ -387,8 +406,10 @@ TEST_F(StanAgradRevOde, initial_state_vv) { for (size_t m = 0; m < M; m++) theta_v[m] = 10 * (m + 1); - coupled_ode_system coupled_system_vv( - base_ode, y0_v, theta_v, x, x_int, &msgs); + coupled_ode_system, std::vector, + std::vector> + coupled_system_vv(base_ode, y0_v, &msgs, theta_v, x, x_int); std::vector state = coupled_system_vv.initial_state(); for (size_t n = 0; n < N; n++) @@ -399,6 +420,7 @@ TEST_F(StanAgradRevOde, initial_state_vv) { for (size_t n = N + N * N; n < N + N * N + N * M; n++) EXPECT_FLOAT_EQ(0.0, state[n]); } + TEST_F(StanAgradRevOde, size_vv) { using stan::math::coupled_ode_system; using stan::math::var; @@ -408,11 +430,14 @@ TEST_F(StanAgradRevOde, size_vv) { const size_t M = 4; const size_t z_size = N + N * N + N * M; - std::vector y0_v(N, 0.0); + Eigen::Matrix y0_v + = Eigen::VectorXd::Zero(N).template cast(); std::vector theta_v(M, 0.0); - coupled_ode_system coupled_system_vv( - base_ode, y0_v, theta_v, x, x_int, &msgs); + coupled_ode_system, std::vector, + std::vector> + coupled_system_vv(base_ode, y0_v, &msgs, theta_v, x, x_int); EXPECT_EQ(z_size, coupled_system_vv.size()); } @@ -426,11 +451,14 @@ TEST_F(StanAgradRevOde, memory_recovery_vv) { const size_t M = 4; const size_t z_size = N + N * N + N * M; - std::vector y0_v(N, 0.0); + Eigen::Matrix y0_v + = Eigen::VectorXd::Zero(N).template cast(); std::vector theta_v(M, 0.0); - coupled_ode_system coupled_system_vv( - base_ode, y0_v, theta_v, x, x_int, &msgs); + coupled_ode_system, std::vector, + std::vector> + coupled_system_vv(base_ode, y0_v, &msgs, theta_v, x, x_int); std::vector z(z_size, 0); std::vector dz_dt(z_size, 0); @@ -456,11 +484,14 @@ TEST_F(StanAgradRevOde, memory_recovery_exception_vv) { SCOPED_TRACE(scoped_message.str()); mock_throwing_ode_functor throwing_ode(message, 1); - std::vector y0_v(N, 0.0); + Eigen::Matrix y0_v + = Eigen::VectorXd::Zero(N).template cast(); std::vector theta_v(M, 0.0); - coupled_ode_system, var, var> - coupled_system_vv(throwing_ode, y0_v, theta_v, x, x_int, &msgs); + coupled_ode_system, std::vector, + std::vector> + coupled_system_vv(throwing_ode, y0_v, &msgs, theta_v, x, x_int); std::vector z(z_size, 0); std::vector dz_dt(z_size, 0); @@ -471,3 +502,244 @@ TEST_F(StanAgradRevOde, memory_recovery_exception_vv) { EXPECT_TRUE(stan::math::empty_nested()); } } + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct ayt { + template + inline auto operator()(const T0& t, const T_y& y, std::ostream* msgs, + const T_Args&... args) const { + std::vector::type> vec + = {sum_(args)...}; + Eigen::Matrix, Eigen::Dynamic, 1> + out(2); + out(0) = -sum_(vec) * y(0) * t; + out(1) = -1.7 * sum_(vec) * y(1) * t; + return out; + } +}; + +TEST_F(StanAgradRevOde, coupled_ode_system_var) { + using stan::math::coupled_ode_system; + using stan::math::var; + + Eigen::VectorXd y0(2); + y0 << 0.1, 0.2; + Eigen::Matrix y0v = y0.template cast(); + + double a = 1.3; + var av = a; + + ayt func; + + coupled_ode_system system(func, y0v, + &msgs, av); + + std::vector z = {y0(0), y0(1), 3.2, 3.3, 4.4, 4.5, 2.1, 2.2}; + + double t0 = 0.75; + + std::vector dz_dt; + system(z, dz_dt, t0); + + EXPECT_FLOAT_EQ(dz_dt[0], -0.0975); + EXPECT_FLOAT_EQ(dz_dt[1], -0.3315); + EXPECT_FLOAT_EQ(dz_dt[2], -3.12000); + EXPECT_FLOAT_EQ(dz_dt[3], -5.46975); + EXPECT_FLOAT_EQ(dz_dt[4], -4.29000); + EXPECT_FLOAT_EQ(dz_dt[5], -7.45875); + EXPECT_FLOAT_EQ(dz_dt[6], -2.1225); + EXPECT_FLOAT_EQ(dz_dt[7], -3.9015); +} + +TEST_F(StanAgradRevOde, coupled_ode_system_std_vector) { + using stan::math::coupled_ode_system; + using stan::math::var; + + Eigen::VectorXd y0(2); + y0 << 0.1, 0.2; + Eigen::Matrix y0v = y0.template cast(); + + std::vector av = {1.3}; + + ayt func; + + coupled_ode_system system(func, y0v, + &msgs, av); + + std::vector z = {y0(0), y0(1), 3.2, 3.3, 4.4, 4.5, 2.1, 2.2}; + + double t0 = 0.75; + + std::vector dz_dt; + system(z, dz_dt, t0); + + EXPECT_FLOAT_EQ(dz_dt[0], -0.0975); + EXPECT_FLOAT_EQ(dz_dt[1], -0.3315); + EXPECT_FLOAT_EQ(dz_dt[2], -3.12000); + EXPECT_FLOAT_EQ(dz_dt[3], -5.46975); + EXPECT_FLOAT_EQ(dz_dt[4], -4.29000); + EXPECT_FLOAT_EQ(dz_dt[5], -7.45875); + EXPECT_FLOAT_EQ(dz_dt[6], -2.1225); + EXPECT_FLOAT_EQ(dz_dt[7], -3.9015); +} + +TEST_F(StanAgradRevOde, coupled_ode_system_vector) { + using stan::math::coupled_ode_system; + using stan::math::var; + + Eigen::VectorXd y0(2); + y0 << 0.1, 0.2; + Eigen::Matrix y0v = y0.template cast(); + + Eigen::Matrix av(1); + av << 1.3; + + ayt func; + + coupled_ode_system system(func, y0v, + &msgs, av); + + std::vector z = {y0(0), y0(1), 3.2, 3.3, 4.4, 4.5, 2.1, 2.2}; + + double t0 = 0.75; + + std::vector dz_dt; + system(z, dz_dt, t0); + + EXPECT_FLOAT_EQ(dz_dt[0], -0.0975); + EXPECT_FLOAT_EQ(dz_dt[1], -0.3315); + EXPECT_FLOAT_EQ(dz_dt[2], -3.12000); + EXPECT_FLOAT_EQ(dz_dt[3], -5.46975); + EXPECT_FLOAT_EQ(dz_dt[4], -4.29000); + EXPECT_FLOAT_EQ(dz_dt[5], -7.45875); + EXPECT_FLOAT_EQ(dz_dt[6], -2.1225); + EXPECT_FLOAT_EQ(dz_dt[7], -3.9015); +} + +TEST_F(StanAgradRevOde, coupled_ode_system_row_vector) { + using stan::math::coupled_ode_system; + using stan::math::var; + + Eigen::VectorXd y0(2); + y0 << 0.1, 0.2; + Eigen::Matrix y0v = y0.template cast(); + + Eigen::Matrix av(1); + av << 1.3; + + ayt func; + + coupled_ode_system system(func, y0v, + &msgs, av); + + std::vector z = {y0(0), y0(1), 3.2, 3.3, 4.4, 4.5, 2.1, 2.2}; + + double t0 = 0.75; + + std::vector dz_dt; + system(z, dz_dt, t0); + + EXPECT_FLOAT_EQ(dz_dt[0], -0.0975); + EXPECT_FLOAT_EQ(dz_dt[1], -0.3315); + EXPECT_FLOAT_EQ(dz_dt[2], -3.12000); + EXPECT_FLOAT_EQ(dz_dt[3], -5.46975); + EXPECT_FLOAT_EQ(dz_dt[4], -4.29000); + EXPECT_FLOAT_EQ(dz_dt[5], -7.45875); + EXPECT_FLOAT_EQ(dz_dt[6], -2.1225); + EXPECT_FLOAT_EQ(dz_dt[7], -3.9015); +} + +TEST_F(StanAgradRevOde, coupled_ode_system_matrix) { + using stan::math::coupled_ode_system; + using stan::math::var; + + Eigen::VectorXd y0(2); + y0 << 0.1, 0.2; + Eigen::Matrix y0v = y0.template cast(); + + Eigen::Matrix av(1, 1); + av << 1.3; + + ayt func; + + coupled_ode_system system(func, y0v, + &msgs, av); + + std::vector z = {y0(0), y0(1), 3.2, 3.3, 4.4, 4.5, 2.1, 2.2}; + + double t0 = 0.75; + + std::vector dz_dt; + system(z, dz_dt, t0); + + EXPECT_FLOAT_EQ(dz_dt[0], -0.0975); + EXPECT_FLOAT_EQ(dz_dt[1], -0.3315); + EXPECT_FLOAT_EQ(dz_dt[2], -3.12000); + EXPECT_FLOAT_EQ(dz_dt[3], -5.46975); + EXPECT_FLOAT_EQ(dz_dt[4], -4.29000); + EXPECT_FLOAT_EQ(dz_dt[5], -7.45875); + EXPECT_FLOAT_EQ(dz_dt[6], -2.1225); + EXPECT_FLOAT_EQ(dz_dt[7], -3.9015); +} + +TEST_F(StanAgradRevOde, coupled_ode_system_extra_args) { + using stan::math::coupled_ode_system; + using stan::math::var; + + Eigen::VectorXd y0(2); + y0 << 0.1, 0.2; + Eigen::Matrix y0v = y0.template cast(); + + Eigen::Matrix av(1, 1); + av << -0.2; + int e1 = 1; + double e2 = 0.1; + std::vector e3 = {0.1}; + Eigen::VectorXd e4(1); + e4 << 0.1; + Eigen::VectorXd e5(1); + e5 << 0.1; + Eigen::MatrixXd e6(1, 1); + e6 << 0.1; + + ayt func; + + coupled_ode_system + system(func, y0v, &msgs, av, e1, e2, e3, e4, e5, e6); + + std::vector z = {y0(0), y0(1), 3.2, 3.3, 4.4, 4.5, 2.1, 2.2}; + + double t0 = 0.75; + + std::vector dz_dt; + system(z, dz_dt, t0); + + EXPECT_FLOAT_EQ(dz_dt[0], -0.0975); + EXPECT_FLOAT_EQ(dz_dt[1], -0.3315); + EXPECT_FLOAT_EQ(dz_dt[2], -3.12000); + EXPECT_FLOAT_EQ(dz_dt[3], -5.46975); + EXPECT_FLOAT_EQ(dz_dt[4], -4.29000); + EXPECT_FLOAT_EQ(dz_dt[5], -7.45875); + EXPECT_FLOAT_EQ(dz_dt[6], -2.1225); + EXPECT_FLOAT_EQ(dz_dt[7], -3.9015); +} diff --git a/test/unit/math/rev/functor/cvodes_ode_data_prim_test.cpp b/test/unit/math/rev/functor/cvodes_ode_data_prim_test.cpp deleted file mode 100644 index 2bfe93e4555..00000000000 --- a/test/unit/math/rev/functor/cvodes_ode_data_prim_test.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -struct StanMathOdeCVode : public ::testing::Test { - void SetUp() { - stan::math::recover_memory(); - t0 = 0; - } - std::stringstream msgs; - std::vector x; - std::vector x_int; - double t0; -}; -TEST_F(StanMathOdeCVode, recover_exception) { - using stan::math::cvodes_ode_data; - std::string message = "ode throws"; - - const int N = 3; - const int M = 4; - - mock_throwing_ode_functor throwing_ode(message); - - std::vector y0_d(N, 0.0); - std::vector theta_v(M, 0.0); - - cvodes_ode_data, double, double> - ode_data_dd(throwing_ode, y0_d, theta_v, x, x_int, &msgs); - - std::vector y(3, 0); - std::vector dy_dt(3, 0); - - N_Vector nv_y = N_VMake_Serial(N, &y[0]); - N_Vector nv_dy_dt = N_VMake_Serial(N, &dy_dt[0]); - - double t = 10; - - EXPECT_THROW_MSG(ode_data_dd.cv_rhs(t, nv_y, nv_dy_dt, &ode_data_dd), - std::logic_error, message); -} diff --git a/test/unit/math/rev/functor/cvodes_ode_data_rev_test.cpp b/test/unit/math/rev/functor/cvodes_ode_data_rev_test.cpp deleted file mode 100644 index 20fe29d8700..00000000000 --- a/test/unit/math/rev/functor/cvodes_ode_data_rev_test.cpp +++ /dev/null @@ -1,460 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct StanMathRevOdeCVode : public ::testing::Test { - void SetUp() { - stan::math::recover_memory(); - t0 = 0; - } - std::stringstream msgs; - std::vector x; - std::vector x_int; - double t0; - N_Vector tmp1; - N_Vector tmp2; -}; - -// helper object to initialize data structures which we can pass into -// the cvodes integrator object -struct cvodes2coupled { - const size_t N_; - const size_t S_; - std::vector state_; - std::vector state_dot_; - double* p_state_; - double* p_state_dot_; - N_Vector cvode_state_; - N_Vector cvode_state_dot_; - N_Vector* cvode_state_sens_; - N_Vector* cvode_state_sens_dot_; - - cvodes2coupled(const size_t N, const size_t S) - : N_(N), - S_(S), - state_(N, 0), - state_dot_(N, 0), - p_state_(&state_[0]), - p_state_dot_(&state_dot_[0]), - cvode_state_(N_VMake_Serial(N_, &state_[0])), - cvode_state_dot_(N_VMake_Serial(N_, &state_dot_[0])) { - if (S_ > 0) { - cvode_state_sens_ = N_VCloneVectorArray_Serial(S_, cvode_state_); - cvode_state_sens_dot_ = N_VCloneVectorArray_Serial(S_, cvode_state_); - - for (size_t s = 0; s < S_; s++) - N_VConst(RCONST(0.0), cvode_state_sens_[s]); - for (size_t s = 0; s < S_; s++) - N_VConst(RCONST(0.0), cvode_state_sens_dot_[s]); - } - } - - ~cvodes2coupled() { - // N_VDestroy_Serial should noop because - // N_VMake_Serial sets own_data to false - N_VDestroy_Serial(cvode_state_); - if (S_ > 0) { - N_VDestroyVectorArray_Serial(cvode_state_sens_, S_); - N_VDestroyVectorArray_Serial(cvode_state_sens_dot_, S_); - } - } - - std::vector get_coupled_state() const { - std::vector y_coupled(N_ + N_ * S_, 0); - - std::copy(state_.begin(), state_.end(), y_coupled.begin()); - - for (size_t s = 0; s < S_; s++) { - for (size_t n = 0; n < N_; n++) { - y_coupled[N_ + s * N_ + n] = NV_Ith_S(cvode_state_sens_[s], n); - } - } - - return (y_coupled); - } - - std::vector get_coupled_state_dot() const { - std::vector y_coupled_dot(N_ + N_ * S_, 0); - - std::copy(state_dot_.begin(), state_dot_.end(), y_coupled_dot.begin()); - - for (size_t s = 0; s < S_; s++) { - for (size_t n = 0; n < N_; n++) { - y_coupled_dot[N_ + s * N_ + n] = NV_Ith_S(cvode_state_sens_dot_[s], n); - } - } - - return (y_coupled_dot); - } -}; - -// ******************** DV **************************** -TEST_F(StanMathRevOdeCVode, cvodes_ode_data_dv) { - using stan::math::cvodes_ode_data; - - harm_osc_ode_fun harm_osc; - - const size_t N = 2; - const int M = 1; - const size_t S = 1; - - std::vector theta; - std::vector y0; - - double gamma(0.15); - t0 = 0; - - theta.push_back(gamma); - y0.push_back(1.0); - y0.push_back(0.5); - - cvodes_ode_data ode_data_dv( - harm_osc, y0, theta, x, x_int, &msgs); - - cvodes2coupled nvec(N, S); - - nvec.state_[0] = 1.0; - nvec.state_[1] = 0.5; - NV_Ith_S(nvec.cvode_state_sens_[0], 0) = 1.0; - NV_Ith_S(nvec.cvode_state_sens_[0], 1) = 2.0; - - ode_data_dv.cv_rhs(t0, nvec.cvode_state_, nvec.cvode_state_dot_, - &ode_data_dv); - - ode_data_dv.cv_rhs_sens(M, t0, nvec.cvode_state_, nvec.cvode_state_dot_, - nvec.cvode_state_sens_, nvec.cvode_state_sens_dot_, - &ode_data_dv, tmp1, tmp2); - - std::vector dy_dt_coupled = nvec.get_coupled_state_dot(); - - EXPECT_FLOAT_EQ(0.5, dy_dt_coupled[0]); - EXPECT_FLOAT_EQ(-1.075, dy_dt_coupled[1]); - EXPECT_FLOAT_EQ(2, dy_dt_coupled[2]); - EXPECT_FLOAT_EQ(-1.8, dy_dt_coupled[3]); -} -TEST_F(StanMathRevOdeCVode, memory_recovery_dv) { - using stan::math::cvodes_ode_data; - using stan::math::var; - mock_ode_functor base_ode; - - const size_t N = 3; - const size_t M = 4; - const size_t S = M; - - std::vector y0_d(N, 0.0); - std::vector theta(M, 0.0); - - cvodes_ode_data ode_data_dv( - base_ode, y0_d, theta, x, x_int, &msgs); - - // std::vector y(3, 0); - // std::vector dy_dt(3, 0); - double t = 10; - - cvodes2coupled nvec(N, S); - - EXPECT_TRUE(stan::math::empty_nested()); - EXPECT_NO_THROW(ode_data_dv.cv_rhs(t, nvec.cvode_state_, - nvec.cvode_state_dot_, &ode_data_dv)); - - EXPECT_NO_THROW(ode_data_dv.cv_rhs_sens( - static_cast(M), t, nvec.cvode_state_, nvec.cvode_state_dot_, - nvec.cvode_state_sens_, nvec.cvode_state_sens_dot_, &ode_data_dv, tmp1, - tmp2)); - EXPECT_TRUE(stan::math::empty_nested()); -} - -TEST_F(StanMathRevOdeCVode, memory_recovery_exception_dv) { - using stan::math::cvodes_ode_data; - using stan::math::var; - std::string message = "ode throws"; - - const size_t N = 3; - const size_t M = 4; - const size_t S = M; - - for (size_t n = 0; n < N + 1; n++) { - std::stringstream scoped_message; - scoped_message << "iteration " << n; - SCOPED_TRACE(scoped_message.str()); - mock_throwing_ode_functor throwing_ode(message, 1); - - std::vector y0_d(N, 0.0); - std::vector theta(M, 0.0); - - cvodes_ode_data, double, var> - ode_data_dv(throwing_ode, y0_d, theta, x, x_int, &msgs); - - std::vector y(3, 0); - std::vector dy_dt(3, 0); - double t = 10; - - cvodes2coupled nvec(N, S); - - EXPECT_TRUE(stan::math::empty_nested()); - EXPECT_THROW_MSG(ode_data_dv.cv_rhs(t, nvec.cvode_state_, - nvec.cvode_state_dot_, &ode_data_dv), - std::logic_error, message); - EXPECT_TRUE(stan::math::empty_nested()); - } -} - -// ******************** VD **************************** - -TEST_F(StanMathRevOdeCVode, cvodes_ode_data_vd) { - using stan::math::cvodes_ode_data; - - harm_osc_ode_fun harm_osc; - - const size_t N = 2; - const int M = 1; - const size_t S = N; - - std::vector theta; - std::vector y0_var; - std::vector y0_d; - double t0; - - double gamma(0.15); - t0 = 0; - - theta.push_back(gamma); - - // note: here the old test was inconsistent in that coupled_y0[0] - // and coupled_y0[1] should have been set to 0 instead. - - // coupled_y0.push_back(1.0); - // coupled_y0.push_back(0.5); - // coupled_y0.push_back(1.0); - // coupled_y0.push_back(3.0); - // coupled_y0.push_back(2.0); - // coupled_y0.push_back(5.0); - - y0_var.push_back(1.0); - y0_var.push_back(0.5); - y0_d.push_back(1.0); - y0_d.push_back(0.5); - - cvodes_ode_data ode_data_vd( - harm_osc, y0_var, theta, x, x_int, &msgs); - - cvodes2coupled nvec(N, S); - - // nvec.state_[0] = 2*y0_d[0]; // use this to match old expectation - // nvec.state_[1] = 2*y0_d[1]; // use this to match old expectation - nvec.state_[0] = y0_d[0]; - nvec.state_[1] = y0_d[1]; - NV_Ith_S(nvec.cvode_state_sens_[0], 0) = 1.0 + 1.0; - NV_Ith_S(nvec.cvode_state_sens_[0], 1) = 3.0; - NV_Ith_S(nvec.cvode_state_sens_[1], 0) = 2.0; - NV_Ith_S(nvec.cvode_state_sens_[1], 1) = 5.0 + 1.0; - - ode_data_vd.cv_rhs(t0, nvec.cvode_state_, nvec.cvode_state_dot_, - &ode_data_vd); - - ode_data_vd.cv_rhs_sens(M, t0, nvec.cvode_state_, nvec.cvode_state_dot_, - nvec.cvode_state_sens_, nvec.cvode_state_sens_dot_, - &ode_data_vd, tmp1, tmp2); - - std::vector dy_dt_coupled = nvec.get_coupled_state_dot(); - - // EXPECT_FLOAT_EQ(1.0, dy_dt_coupled[0]); // old expectation - // EXPECT_FLOAT_EQ(-2.0 - 0.15 * 1.0, dy_dt_coupled[1]); // old expectation - EXPECT_FLOAT_EQ(0.5, dy_dt_coupled[0]); - EXPECT_FLOAT_EQ(-1.0 - 0.15 * 0.5, dy_dt_coupled[1]); - EXPECT_FLOAT_EQ(0 + 1.0 * 0 + 3.0 * 1 + 0, dy_dt_coupled[2]); - EXPECT_FLOAT_EQ(-1.0 - 1.0 * 1.0 - 0.15 * 3.0, dy_dt_coupled[3]); - EXPECT_FLOAT_EQ(1.0 + 2.0 * 0 + 5.0 * 1.0, dy_dt_coupled[4]); - EXPECT_FLOAT_EQ(-0.15 - 1.0 * 2.0 - 0.15 * 5.0, dy_dt_coupled[5]); -} -TEST_F(StanMathRevOdeCVode, memory_recovery_vd) { - using stan::math::cvodes_ode_data; - using stan::math::var; - mock_ode_functor base_ode; - - const size_t N = 3; - const size_t M = 4; - const size_t S = N; - - std::vector y0_v(N, 0.0); - std::vector theta_d(M, 0.0); - - cvodes_ode_data ode_data_vd( - base_ode, y0_v, theta_d, x, x_int, &msgs); - - // std::vector y(3, 0); - // std::vector dy_dt(3, 0); - double t = 10; - - cvodes2coupled nvec(N, S); - - EXPECT_TRUE(stan::math::empty_nested()); - EXPECT_NO_THROW(ode_data_vd.cv_rhs(t, nvec.cvode_state_, - nvec.cvode_state_dot_, &ode_data_vd)); - - EXPECT_NO_THROW(ode_data_vd.cv_rhs_sens( - static_cast(M), t, nvec.cvode_state_, nvec.cvode_state_dot_, - nvec.cvode_state_sens_, nvec.cvode_state_sens_dot_, &ode_data_vd, tmp1, - tmp2)); - EXPECT_TRUE(stan::math::empty_nested()); -} - -TEST_F(StanMathRevOdeCVode, memory_recovery_exception_vd) { - using stan::math::cvodes_ode_data; - using stan::math::var; - std::string message = "ode throws"; - - const size_t N = 3; - const size_t M = 4; - const size_t S = N; - for (size_t n = 0; n < N + 1; n++) { - std::stringstream scoped_message; - scoped_message << "iteration " << n; - SCOPED_TRACE(scoped_message.str()); - mock_throwing_ode_functor throwing_ode(message, 1); - - std::vector y0_v(N, 0.0); - std::vector theta_d(M, 0.0); - - cvodes_ode_data, var, double> - ode_data_vd(throwing_ode, y0_v, theta_d, x, x_int, &msgs); - - double t = 10; - - cvodes2coupled nvec(N, S); - - EXPECT_TRUE(stan::math::empty_nested()); - EXPECT_THROW_MSG(ode_data_vd.cv_rhs(t, nvec.cvode_state_, - nvec.cvode_state_dot_, &ode_data_vd), - std::logic_error, message); - EXPECT_TRUE(stan::math::empty_nested()); - } -} - -// ******************** VV **************************** - -TEST_F(StanMathRevOdeCVode, cvodes_ode_data_vv) { - using stan::math::cvodes_ode_data; - using stan::math::var; - - const size_t N = 2; - const int M = 1; - const size_t S = 1 + 2; - - std::vector y0_v; - y0_v.push_back(1.0); - y0_v.push_back(0.5); - - std::vector theta_v; - theta_v.push_back(0.15); - - harm_osc_ode_fun harm_osc; - - cvodes_ode_data - ode_data_vv(harm_osc, y0_v, theta_v, x, x_int, &msgs); - - t0 = 0; - - std::vector y0_d(2); - y0_d[0] = 1.0; - y0_d[1] = 0.5; - - std::vector theta_d(1); - theta_d[0] = 0.15; - - cvodes2coupled nvec(N, S); - - nvec.state_ = y0_d; - NV_Ith_S(nvec.cvode_state_sens_[0], 0) = 1.0; - NV_Ith_S(nvec.cvode_state_sens_[0], 1) = 0.0; - NV_Ith_S(nvec.cvode_state_sens_[1], 0) = 0.0; - NV_Ith_S(nvec.cvode_state_sens_[1], 1) = 1.0; - - ode_data_vv.cv_rhs(t0, nvec.cvode_state_, nvec.cvode_state_dot_, - &ode_data_vv); - - ode_data_vv.cv_rhs_sens(M, t0, nvec.cvode_state_, nvec.cvode_state_dot_, - nvec.cvode_state_sens_, nvec.cvode_state_sens_dot_, - &ode_data_vv, tmp1, tmp2); - - std::vector dy_dt_coupled = nvec.get_coupled_state_dot(); - - std::vector dy_dt_base - = harm_osc(0.0, y0_d, theta_d, x, x_int, &msgs); - - EXPECT_FLOAT_EQ(dy_dt_base[0], dy_dt_coupled[0]); - EXPECT_FLOAT_EQ(dy_dt_base[1], dy_dt_coupled[1]); - EXPECT_FLOAT_EQ(0, dy_dt_coupled[2]); - EXPECT_FLOAT_EQ(-1, dy_dt_coupled[3]); - EXPECT_FLOAT_EQ(1, dy_dt_coupled[4]); - EXPECT_FLOAT_EQ(-0.15, dy_dt_coupled[5]); - EXPECT_FLOAT_EQ(0, dy_dt_coupled[6]); - EXPECT_FLOAT_EQ(-0.5, dy_dt_coupled[7]); -} -TEST_F(StanMathRevOdeCVode, memory_recovery_vv) { - using stan::math::cvodes_ode_data; - using stan::math::var; - mock_ode_functor base_ode; - - const size_t N = 3; - const size_t M = 4; - const size_t S = N + M; - - std::vector y0_v(N, 0.0); - std::vector theta_v(M, 0.0); - - cvodes_ode_data ode_data_vv( - base_ode, y0_v, theta_v, x, x_int, &msgs); - - double t = 10; - - cvodes2coupled nvec(N, S); - - EXPECT_TRUE(stan::math::empty_nested()); - EXPECT_NO_THROW(ode_data_vv.cv_rhs(t, nvec.cvode_state_, - nvec.cvode_state_dot_, &ode_data_vv)); - - EXPECT_NO_THROW(ode_data_vv.cv_rhs_sens( - static_cast(M), t, nvec.cvode_state_, nvec.cvode_state_dot_, - nvec.cvode_state_sens_, nvec.cvode_state_sens_dot_, &ode_data_vv, tmp1, - tmp2)); - EXPECT_TRUE(stan::math::empty_nested()); -} - -TEST_F(StanMathRevOdeCVode, memory_recovery_exception_vv) { - using stan::math::cvodes_ode_data; - using stan::math::var; - std::string message = "ode throws"; - - const size_t N = 3; - const size_t M = 4; - const size_t S = N + M; - for (size_t n = 0; n < N + 1; n++) { - std::stringstream scoped_message; - scoped_message << "iteration " << n; - SCOPED_TRACE(scoped_message.str()); - mock_throwing_ode_functor throwing_ode(message, 1); - - std::vector y0_v(N, 0.0); - std::vector theta_v(M, 0.0); - - cvodes_ode_data, var, var> - ode_data_vv(throwing_ode, y0_v, theta_v, x, x_int, &msgs); - - double t = 10; - - cvodes2coupled nvec(N, S); - - EXPECT_TRUE(stan::math::empty_nested()); - EXPECT_THROW_MSG(ode_data_vv.cv_rhs(t, nvec.cvode_state_, - nvec.cvode_state_dot_, &ode_data_vv), - std::logic_error, message); - EXPECT_TRUE(stan::math::empty_nested()); - } -} diff --git a/test/unit/math/rev/functor/cvodes_utils_test.cpp b/test/unit/math/rev/functor/cvodes_utils_test.cpp new file mode 100644 index 00000000000..31ef4c77108 --- /dev/null +++ b/test/unit/math/rev/functor/cvodes_utils_test.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +struct Inverse { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> operator()( + const T0& t, const Eigen::Matrix& y, + std::ostream* msgs) const { + Eigen::Matrix out(1); + out(0) = 1.0 / (y(0) - 1.0); + return out; + } +}; + +TEST(StanMath, cvodes_error_handler) { + Eigen::VectorXd y0 = Eigen::VectorXd::Ones(1); + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + std::string msg + = "CVODES: CVode Internal t = 0 and h = 0 are such that t + h = t on the " + "next step"; + + EXPECT_THROW_MSG(stan::math::ode_bdf(Inverse(), y0, t0, ts, nullptr), + std::domain_error, msg); +} diff --git a/test/unit/math/rev/functor/integrate_ode_adams_prim_test.cpp b/test/unit/math/rev/functor/integrate_ode_adams_prim_test.cpp index 819c8d03e85..fabc51ae711 100644 --- a/test/unit/math/rev/functor/integrate_ode_adams_prim_test.cpp +++ b/test/unit/math/rev/functor/integrate_ode_adams_prim_test.cpp @@ -118,7 +118,7 @@ TEST(StanMathOde_integrate_ode_adams, error_conditions) { ts_bad.push_back(1); EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts_bad, theta, x, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "times is not a valid ordered vector"); + std::domain_error, "times is not a valid sorted vector"); // TODO(carpenter): g++6 failure std::vector theta_bad; @@ -140,15 +140,15 @@ TEST(StanMathOde_integrate_ode_adams, error_conditions) { EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta, x, x_int, 0, -1, 1e-6, 10), - std::invalid_argument, "relative_tolerance"); + std::domain_error, "relative_tolerance"); EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta, x, x_int, 0, 1e-6, -1, 10), - std::invalid_argument, "absolute_tolerance"); + std::domain_error, "absolute_tolerance"); EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta, x, x_int, 0, 1e-6, 1e-6, -1), - std::invalid_argument, "max_num_steps"); + std::domain_error, "max_num_steps"); } TEST(StanMathOde_integrate_ode_adams, error_conditions_nan) { @@ -208,7 +208,7 @@ TEST(StanMathOde_integrate_ode_adams, error_conditions_nan) { theta_bad[0] = nan; EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_nan.str()); @@ -218,7 +218,7 @@ TEST(StanMathOde_integrate_ode_adams, error_conditions_nan) { x_bad[0] = nan; EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_nan.str()); @@ -305,14 +305,14 @@ TEST(StanMathOde_integrate_ode_adams, error_conditions_inf) { theta_bad[0] = inf; EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_inf.str()); theta_bad[0] = -inf; EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_neg_inf.str()); @@ -322,14 +322,14 @@ TEST(StanMathOde_integrate_ode_adams, error_conditions_inf) { x_bad[0] = inf; EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_inf.str()); x_bad[0] = -inf; EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_neg_inf.str()); @@ -357,7 +357,7 @@ TEST(StanMathOde_integrate_ode_adams, error_conditions_bad_ode) { std::vector x_int(2, 0); std::string error_msg - = "cvodes_ode_data: dz_dt (3) and states (2) must match in size"; + = "cvodes_integrator: dy_dt (3) and states (2) must match in size"; EXPECT_THROW_MSG(integrate_ode_adams(harm_osc, y0, t0, ts, theta, x, x_int, 0, 1e-8, 1e-10, 1e6), diff --git a/test/unit/math/rev/functor/integrate_ode_adams_rev_test.cpp b/test/unit/math/rev/functor/integrate_ode_adams_rev_test.cpp index 85805ac2eb9..ef6a9ae0fa7 100644 --- a/test/unit/math/rev/functor/integrate_ode_adams_rev_test.cpp +++ b/test/unit/math/rev/functor/integrate_ode_adams_rev_test.cpp @@ -129,7 +129,7 @@ TEST(StanAgradRevOde_integrate_ode_adams, harmonic_oscillator_error) { // aligned error handling with non-stiff case std::string error_msg - = "cvodes_ode_data: dz_dt (3) and states (2) must match in size"; + = "cvodes_integrator: dy_dt (3) and states (2) must match in size"; sho_error_test(harm_osc, y0, t0, ts, theta, x, x_int, error_msg); sho_error_test(harm_osc, y0, t0, ts, theta, x, x_int, error_msg); @@ -238,42 +238,46 @@ TEST(StanAgradRevOde_integrate_ode_adams, t0_as_param_AD) { using stan::math::value_of; using stan::math::var; const double t0 = 0.0; - const int nt = 100; // nb. of time steps - const int ns = 2; // nb. of states std::ostream* msgs = NULL; harm_osc_ode_fun ode; - std::vector theta{0.15, 0.25}; + std::vector theta{0.15}; std::vector y0{1.0, 0.0}; - std::vector ts; - for (int i = 0; i < nt; i++) - ts.push_back(t0 + 0.1 * (i + 1)); + std::vector ts = {5.0, 10.0}; std::vector x; std::vector x_int; std::vector y0v = to_var(y0); std::vector thetav = to_var(theta); - stan::math::var t0v = 0.0; + stan::math::var t0v = to_var(t0); std::vector > res; - auto test_ad = [&res, &t0v, &ode, &nt, &ns, &theta, &x, &x_int, &msgs]() { - for (auto i = 0; i < nt; ++i) { - std::vector res_d = value_of(res[i]); - res[i][0].grad(); - EXPECT_FLOAT_EQ(t0v.adj(), 0.0); - stan::math::set_zero_all_adjoints(); - res[i][1].grad(); - EXPECT_FLOAT_EQ(t0v.adj(), 1.0); - stan::math::set_zero_all_adjoints(); - } + auto test_ad = [&res, &t0v, &ode, &theta, &x, &x_int, &msgs]() { + res[0][0].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), -0.66360742442816977871); + stan::math::set_zero_all_adjoints(); + res[0][1].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), 0.23542843380353062344); + stan::math::set_zero_all_adjoints(); + res[1][0].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), -0.2464078910913158893); + stan::math::set_zero_all_adjoints(); + res[1][1].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), -0.38494826636037426937); + stan::math::set_zero_all_adjoints(); }; - res = integrate_ode_adams(ode, y0, t0v, ts, theta, x, x_int); + res = integrate_ode_adams(ode, y0, t0v, ts, theta, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); - res = integrate_ode_adams(ode, y0v, t0v, ts, theta, x, x_int); + res = integrate_ode_adams(ode, y0v, t0v, ts, theta, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); - res = integrate_ode_adams(ode, y0, t0v, ts, thetav, x, x_int); + res = integrate_ode_adams(ode, y0, t0v, ts, thetav, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); - res = integrate_ode_adams(ode, y0v, t0v, ts, thetav, x, x_int); + res = integrate_ode_adams(ode, y0v, t0v, ts, thetav, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); + stan::math::recover_memory(); } diff --git a/test/unit/math/rev/functor/integrate_ode_bdf_prim_test.cpp b/test/unit/math/rev/functor/integrate_ode_bdf_prim_test.cpp index d6f2ee5da6a..9d23e38abc6 100644 --- a/test/unit/math/rev/functor/integrate_ode_bdf_prim_test.cpp +++ b/test/unit/math/rev/functor/integrate_ode_bdf_prim_test.cpp @@ -117,7 +117,7 @@ TEST(StanMathOde_integrate_ode_bdf, error_conditions) { ts_bad.push_back(1); EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts_bad, theta, x, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "times is not a valid ordered vector"); + std::domain_error, "times is not a valid sorted vector"); // TODO(carpenter): g++6 failure std::vector theta_bad; @@ -139,15 +139,15 @@ TEST(StanMathOde_integrate_ode_bdf, error_conditions) { EXPECT_THROW_MSG( integrate_ode_bdf(harm_osc, y0, t0, ts, theta, x, x_int, 0, -1, 1e-6, 10), - std::invalid_argument, "relative_tolerance"); + std::domain_error, "relative_tolerance"); EXPECT_THROW_MSG( integrate_ode_bdf(harm_osc, y0, t0, ts, theta, x, x_int, 0, 1e-6, -1, 10), - std::invalid_argument, "absolute_tolerance"); + std::domain_error, "absolute_tolerance"); EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta, x, x_int, 0, 1e-6, 1e-6, -1), - std::invalid_argument, "max_num_steps"); + std::domain_error, "max_num_steps"); } TEST(StanMathOde_integrate_ode_bdf, error_conditions_nan) { @@ -207,7 +207,7 @@ TEST(StanMathOde_integrate_ode_bdf, error_conditions_nan) { theta_bad[0] = nan; EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_nan.str()); @@ -217,7 +217,7 @@ TEST(StanMathOde_integrate_ode_bdf, error_conditions_nan) { x_bad[0] = nan; EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_nan.str()); @@ -304,14 +304,14 @@ TEST(StanMathOde_integrate_ode_bdf, error_conditions_inf) { theta_bad[0] = inf; EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_inf.str()); theta_bad[0] = -inf; EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta_bad, x, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_neg_inf.str()); @@ -321,14 +321,14 @@ TEST(StanMathOde_integrate_ode_bdf, error_conditions_inf) { x_bad[0] = inf; EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_inf.str()); x_bad[0] = -inf; EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta, x_bad, x_int, 0, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_neg_inf.str()); @@ -356,7 +356,7 @@ TEST(StanMathOde_integrate_ode_bdf, error_conditions_bad_ode) { std::vector x_int(2, 0); std::string error_msg - = "cvodes_ode_data: dz_dt (3) and states (2) must match in size"; + = "cvodes_integrator: dy_dt (3) and states (2) must match in size"; EXPECT_THROW_MSG(integrate_ode_bdf(harm_osc, y0, t0, ts, theta, x, x_int, 0, 1e-8, 1e-10, 1e6), diff --git a/test/unit/math/rev/functor/integrate_ode_bdf_rev_test.cpp b/test/unit/math/rev/functor/integrate_ode_bdf_rev_test.cpp index 2f12f727de0..12bc82eb515 100644 --- a/test/unit/math/rev/functor/integrate_ode_bdf_rev_test.cpp +++ b/test/unit/math/rev/functor/integrate_ode_bdf_rev_test.cpp @@ -26,6 +26,8 @@ void sho_value_test(F harm_osc, std::vector& y0, double t0, EXPECT_NEAR(-0.421907, ode_res_vd[99][0].val(), 1e-5); EXPECT_NEAR(0.246407, ode_res_vd[99][1].val(), 1e-5); + + stan::math::recover_memory(); } void sho_finite_diff_test(double t0) { @@ -129,7 +131,7 @@ TEST(StanAgradRevOde_integrate_ode_bdf, harmonic_oscillator_error) { // aligned error handling with non-stiff case std::string error_msg - = "cvodes_ode_data: dz_dt (3) and states (2) must match in size"; + = "cvodes_integrator: dy_dt (3) and states (2) must match in size"; sho_error_test(harm_osc, y0, t0, ts, theta, x, x_int, error_msg); sho_error_test(harm_osc, y0, t0, ts, theta, x, x_int, error_msg); @@ -159,7 +161,7 @@ TEST(StanAgradRevOde_integrate_ode_bdf, lorenz_finite_diff) { for (int i = 0; i < 100; i++) ts.push_back(0.1 * (i + 1)); - test_ode_cvode(lorenz, t0, ts, y0, theta, x, x_int, 1e-8, 1e-1); + test_ode_cvode(lorenz, t0, ts, y0, theta, x, x_int, 1e-6, 1e-1); } TEST(StanAgradRevOde_integrate_ode_bdf, time_steps_as_param) { @@ -202,6 +204,8 @@ TEST(StanAgradRevOde_integrate_ode_bdf, time_steps_as_param) { test_val(); res = integrate_ode_bdf(ode, y0v, t0v, ts, thetav, x, x_int); test_val(); + + stan::math::recover_memory(); } TEST(StanAgradRevOde_integrate_ode_bdf, time_steps_as_param_AD) { @@ -234,13 +238,13 @@ TEST(StanAgradRevOde_integrate_ode_bdf, time_steps_as_param_AD) { std::vector res_d = value_of(res[i]); for (auto j = 0; j < ns; ++j) { g.clear(); - res[i][j].grad(ts, g); + res[i][j].grad(); for (auto k = 0; k < nt; ++k) { if (k != i) { - EXPECT_FLOAT_EQ(g[k], 0.0); + EXPECT_FLOAT_EQ(ts[k].adj(), 0.0); } else { std::vector y0(res_d.begin(), res_d.begin() + ns); - EXPECT_FLOAT_EQ(g[k], + EXPECT_FLOAT_EQ(ts[k].adj(), ode(ts[i].val(), y0, theta, x, x_int, msgs)[j]); } } @@ -264,42 +268,46 @@ TEST(StanAgradRevOde_integrate_ode_bdf, t0_as_param_AD) { using stan::math::value_of; using stan::math::var; const double t0 = 0.0; - const int nt = 100; // nb. of time steps - const int ns = 2; // nb. of states std::ostream* msgs = NULL; harm_osc_ode_fun ode; - std::vector theta{0.15, 0.25}; + std::vector theta{0.15}; std::vector y0{1.0, 0.0}; - std::vector ts; - for (int i = 0; i < nt; i++) - ts.push_back(t0 + 0.1 * (i + 1)); + std::vector ts = {5.0, 10.0}; std::vector x; std::vector x_int; std::vector y0v = to_var(y0); std::vector thetav = to_var(theta); - stan::math::var t0v = 0.0; + stan::math::var t0v = to_var(t0); std::vector > res; - auto test_ad = [&res, &t0v, &ode, &nt, &ns, &theta, &x, &x_int, &msgs]() { - for (auto i = 0; i < nt; ++i) { - std::vector res_d = value_of(res[i]); - res[i][0].grad(); - EXPECT_FLOAT_EQ(t0v.adj(), 0.0); - stan::math::set_zero_all_adjoints(); - res[i][1].grad(); - EXPECT_FLOAT_EQ(t0v.adj(), 1.0); - stan::math::set_zero_all_adjoints(); - } + auto test_ad = [&res, &t0v, &ode, &theta, &x, &x_int, &msgs]() { + res[0][0].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), -0.66360742442816977871); + stan::math::set_zero_all_adjoints(); + res[0][1].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), 0.23542843380353062344); + stan::math::set_zero_all_adjoints(); + res[1][0].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), -0.2464078910913158893); + stan::math::set_zero_all_adjoints(); + res[1][1].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), -0.38494826636037426937); + stan::math::set_zero_all_adjoints(); }; - res = integrate_ode_bdf(ode, y0, t0v, ts, theta, x, x_int); + res = integrate_ode_bdf(ode, y0, t0v, ts, theta, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); - res = integrate_ode_bdf(ode, y0v, t0v, ts, theta, x, x_int); + res = integrate_ode_bdf(ode, y0v, t0v, ts, theta, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); - res = integrate_ode_bdf(ode, y0, t0v, ts, thetav, x, x_int); + res = integrate_ode_bdf(ode, y0, t0v, ts, thetav, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); - res = integrate_ode_bdf(ode, y0v, t0v, ts, thetav, x, x_int); + res = integrate_ode_bdf(ode, y0v, t0v, ts, thetav, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); + stan::math::recover_memory(); } diff --git a/test/unit/math/rev/functor/integrate_ode_cvodes_grad_rev_test.cpp b/test/unit/math/rev/functor/integrate_ode_cvodes_grad_rev_test.cpp index 1c055015098..8794d29903d 100644 --- a/test/unit/math/rev/functor/integrate_ode_cvodes_grad_rev_test.cpp +++ b/test/unit/math/rev/functor/integrate_ode_cvodes_grad_rev_test.cpp @@ -41,7 +41,7 @@ class sho_functor { const std::vector& theta, // parameters const std::vector& x, // double data const std::vector& x_int, // integer data - std::ostream* msgs) const { + std::ostream* msgs) const { // error stream if (y_in.size() != 2) throw std::domain_error("Functor called with inconsistent state"); diff --git a/test/unit/math/rev/functor/integrate_ode_rk45_test.cpp b/test/unit/math/rev/functor/integrate_ode_rk45_rev_test.cpp similarity index 87% rename from test/unit/math/rev/functor/integrate_ode_rk45_test.cpp rename to test/unit/math/rev/functor/integrate_ode_rk45_rev_test.cpp index 1f12fb7845d..1847bc7bb8d 100644 --- a/test/unit/math/rev/functor/integrate_ode_rk45_test.cpp +++ b/test/unit/math/rev/functor/integrate_ode_rk45_rev_test.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -229,42 +229,45 @@ TEST(StanAgradRevOde_integrate_ode_rk45, t0_as_param_AD) { using stan::math::value_of; using stan::math::var; const double t0 = 0.0; - const int nt = 100; // nb. of time steps - const int ns = 2; // nb. of states std::ostream* msgs = NULL; harm_osc_ode_fun ode; std::vector theta{0.15}; std::vector y0{1.0, 0.0}; - std::vector ts; - for (int i = 0; i < nt; i++) - ts.push_back(t0 + 0.1 * (i + 1)); + std::vector ts = {5.0, 10.0}; std::vector x; std::vector x_int; std::vector y0v = to_var(y0); std::vector thetav = to_var(theta); - stan::math::var t0v = 0.0; + stan::math::var t0v = to_var(t0); std::vector> res; - auto test_ad = [&res, &t0v, &ode, &nt, &ns, &theta, &x, &x_int, &msgs]() { - for (auto i = 0; i < nt; ++i) { - std::vector res_d = value_of(res[i]); - res[i][0].grad(); - EXPECT_FLOAT_EQ(t0v.adj(), 0.0); - stan::math::set_zero_all_adjoints(); - res[i][1].grad(); - EXPECT_FLOAT_EQ(t0v.adj(), 1.0); - stan::math::set_zero_all_adjoints(); - } + auto test_ad = [&res, &t0v, &ode, &theta, &x, &x_int, &msgs]() { + res[0][0].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), -0.66360742442816977871); + stan::math::set_zero_all_adjoints(); + res[0][1].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), 0.23542843380353062344); + stan::math::set_zero_all_adjoints(); + res[1][0].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), -0.2464078910913158893); + stan::math::set_zero_all_adjoints(); + res[1][1].grad(); + EXPECT_FLOAT_EQ(t0v.adj(), -0.38494826636037426937); + stan::math::set_zero_all_adjoints(); }; - res = integrate_ode_rk45(ode, y0, t0v, ts, theta, x, x_int); + res = integrate_ode_rk45(ode, y0, t0v, ts, theta, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); - res = integrate_ode_rk45(ode, y0v, t0v, ts, theta, x, x_int); + res = integrate_ode_rk45(ode, y0v, t0v, ts, theta, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); - res = integrate_ode_rk45(ode, y0, t0v, ts, thetav, x, x_int); + res = integrate_ode_rk45(ode, y0, t0v, ts, thetav, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); - res = integrate_ode_rk45(ode, y0v, t0v, ts, thetav, x, x_int); + res = integrate_ode_rk45(ode, y0v, t0v, ts, thetav, x, x_int, nullptr, 1e-10, + 1e-10, 1e6); test_ad(); } diff --git a/test/unit/math/rev/functor/integrate_ode_std_vector_interface_adapter_test.cpp b/test/unit/math/rev/functor/integrate_ode_std_vector_interface_adapter_test.cpp new file mode 100644 index 00000000000..f8611f7f57a --- /dev/null +++ b/test/unit/math/rev/functor/integrate_ode_std_vector_interface_adapter_test.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include + +TEST(StanMathRev, vd) { + using stan::math::var; + harm_osc_ode_data_fun harm_osc; + stan::math::internal::integrate_ode_std_vector_interface_adapter< + harm_osc_ode_data_fun> + harm_osc_adapted(harm_osc); + + std::vector theta = {0.15}; + std::vector y = {1.0, 0.5}; + + std::vector x(3, 1); + std::vector x_int(2, 0); + + double t = 1.0; + + Eigen::Matrix out1 + = stan::math::to_vector(harm_osc(t, y, theta, x, x_int, nullptr)); + Eigen::Matrix out2 + = harm_osc_adapted(t, stan::math::to_vector(y), nullptr, theta, x, x_int); + + stan::math::sum(out1).grad(); + Eigen::VectorXd adjs1(theta.size()); + for (size_t i = 0; i < theta.size(); ++i) + adjs1(i) = theta[i].adj(); + + stan::math::set_zero_all_adjoints(); + + stan::math::sum(out2).grad(); + Eigen::VectorXd adjs2(theta.size()); + for (size_t i = 0; i < theta.size(); ++i) + adjs2(i) = theta[i].adj(); + + EXPECT_MATRIX_FLOAT_EQ(out1.val(), out2.val()); + EXPECT_MATRIX_FLOAT_EQ(adjs1, adjs2); +} + +TEST(StanMathRev, dv) { + using stan::math::var; + harm_osc_ode_data_fun harm_osc; + stan::math::internal::integrate_ode_std_vector_interface_adapter< + harm_osc_ode_data_fun> + harm_osc_adapted(harm_osc); + + std::vector theta = {0.15}; + std::vector y = {1.0, 0.5}; + + std::vector x(3, 1); + std::vector x_int(2, 0); + + double t = 1.0; + + Eigen::Matrix out1 + = stan::math::to_vector(harm_osc(t, y, theta, x, x_int, nullptr)); + Eigen::Matrix out2 + = harm_osc_adapted(t, stan::math::to_vector(y), nullptr, theta, x, x_int); + + stan::math::sum(out1).grad(); + Eigen::VectorXd adjs1(y.size()); + for (size_t i = 0; i < y.size(); ++i) + adjs1(i) = y[i].adj(); + + stan::math::set_zero_all_adjoints(); + + stan::math::sum(out2).grad(); + Eigen::VectorXd adjs2(y.size()); + for (size_t i = 0; i < y.size(); ++i) + adjs2(i) = y[i].adj(); + + EXPECT_MATRIX_FLOAT_EQ(out1.val(), out2.val()); + EXPECT_MATRIX_FLOAT_EQ(adjs1, adjs2); +} + +TEST(StanMathRev, vv) { + using stan::math::var; + harm_osc_ode_data_fun harm_osc; + stan::math::internal::integrate_ode_std_vector_interface_adapter< + harm_osc_ode_data_fun> + harm_osc_adapted(harm_osc); + + std::vector theta = {0.15}; + std::vector y = {1.0, 0.5}; + + std::vector x(3, 1); + std::vector x_int(2, 0); + + double t = 1.0; + + Eigen::Matrix out1 + = stan::math::to_vector(harm_osc(t, y, theta, x, x_int, nullptr)); + Eigen::Matrix out2 + = harm_osc_adapted(t, stan::math::to_vector(y), nullptr, theta, x, x_int); + + stan::math::sum(out1).grad(); + Eigen::VectorXd adjs_theta_1(theta.size()); + for (size_t i = 0; i < theta.size(); ++i) + adjs_theta_1(i) = theta[i].adj(); + Eigen::VectorXd adjs_y_1(y.size()); + for (size_t i = 0; i < y.size(); ++i) + adjs_y_1(i) = y[i].adj(); + + stan::math::set_zero_all_adjoints(); + + stan::math::sum(out2).grad(); + Eigen::VectorXd adjs_theta_2(theta.size()); + for (size_t i = 0; i < theta.size(); ++i) + adjs_theta_2(i) = theta[i].adj(); + Eigen::VectorXd adjs_y_2(y.size()); + for (size_t i = 0; i < y.size(); ++i) + adjs_y_2(i) = y[i].adj(); + + EXPECT_MATRIX_FLOAT_EQ(out1.val(), out2.val()); + EXPECT_MATRIX_FLOAT_EQ(adjs_theta_1, adjs_theta_2); + EXPECT_MATRIX_FLOAT_EQ(adjs_y_1, adjs_y_2); +} diff --git a/test/unit/math/rev/functor/ode_adams_prim_test.cpp b/test/unit/math/rev/functor/ode_adams_prim_test.cpp new file mode 100644 index 00000000000..5476aea7da2 --- /dev/null +++ b/test/unit/math/rev/functor/ode_adams_prim_test.cpp @@ -0,0 +1,312 @@ +#include +#include +#include +#include +#include + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +struct CosArgWrongSize { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(2); + out << stan::math::cos(sum_(vec) * t), 0; + return out; + } +}; + +TEST(ode_adams_prim, y0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + Eigen::VectorXd y0inf(1); + Eigen::VectorXd y0NaN(1); + Eigen::VectorXd y0_empty; + y0NaN << stan::math::NOT_A_NUMBER; + y0inf << stan::math::INFTY; + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0inf, t0, ts, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0NaN, t0, ts, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0_empty, t0, ts, nullptr, a), + std::invalid_argument); +} + +TEST(ode_adams_prim, t0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + double t0inf = stan::math::INFTY; + double t0NaN = stan::math::NOT_A_NUMBER; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0inf, ts, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0NaN, ts, nullptr, a), + std::domain_error); +} + +TEST(ode_adams_prim, ts_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + std::vector ts_repeat = {0.45, 0.45}; + std::vector ts_lots = {0.45, 0.45, 1.1, 1.1, 2.0}; + std::vector ts_empty = {}; + std::vector ts_early = {-0.45, 0.2}; + std::vector ts_decreasing = {0.45, 0.2}; + std::vector tsinf = {stan::math::INFTY, 1.1}; + std::vector tsNaN = {0.45, stan::math::NOT_A_NUMBER}; + + double a = 1.5; + + std::vector out; + EXPECT_NO_THROW(out + = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a)); + EXPECT_EQ(out.size(), ts.size()); + + EXPECT_NO_THROW( + out = stan::math::ode_adams(CosArg1(), y0, t0, ts_repeat, nullptr, a)); + EXPECT_EQ(out.size(), ts_repeat.size()); + EXPECT_MATRIX_FLOAT_EQ(out[0], out[1]); + + EXPECT_NO_THROW( + out = stan::math::ode_adams(CosArg1(), y0, t0, ts_lots, nullptr, a)); + EXPECT_EQ(out.size(), ts_lots.size()); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts_empty, nullptr, a), + std::invalid_argument); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts_early, nullptr, a), + std::domain_error); + + EXPECT_THROW( + stan::math::ode_adams(CosArg1(), y0, t0, ts_decreasing, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, tsinf, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, tsNaN, nullptr, a), + std::domain_error); +} + +TEST(ode_adams_prim, one_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, va)); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, ea)); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, vva)); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, vea)); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, veaNaN), + std::domain_error); +} + +TEST(ode_adams_prim, two_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, a)); + + EXPECT_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, va)); + + EXPECT_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, ea)); + + EXPECT_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, eaNaN), + std::domain_error); + + EXPECT_NO_THROW( + stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, vva)); + + EXPECT_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW( + stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, vea)); + + EXPECT_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a, veaNaN), + std::domain_error); +} + +TEST(ode_adams_prim, rhs_wrong_size_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams(CosArgWrongSize(), y0, t0, ts, nullptr, a), + std::invalid_argument); +} + +TEST(ode_adams_prim, error_name) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double ainf = stan::math::INFTY; + + EXPECT_THROW_MSG(stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, ainf), + std::domain_error, "ode_adams"); +} diff --git a/test/unit/math/rev/functor/ode_adams_rev_test.cpp b/test/unit/math/rev/functor/ode_adams_rev_test.cpp new file mode 100644 index 00000000000..8a1958b8d64 --- /dev/null +++ b/test/unit/math/rev/functor/ode_adams_rev_test.cpp @@ -0,0 +1,547 @@ +#include +#include +#include +#include + +using stan::math::var; + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const T_y& y, std::ostream* msgs, + const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out( + 1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +TEST(StanMathOde_ode_adams, int_t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a); + + EXPECT_NEAR(output[0][0], 0.4165982112, 1e-5); + EXPECT_NEAR(output[1][0], 0.66457668563, 1e-5); +} + +TEST(StanMathOde_ode_adams, int_ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1, 2}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a); + + EXPECT_NEAR(output[0][0], 0.6649966577, 1e-5); + EXPECT_NEAR(output[1][0], 0.09408000537, 1e-5); +} + +TEST(StanMathOde_ode_adams, t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + var t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a); + + output[0][0].grad(); + + EXPECT_NEAR(output[0][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(t0.adj(), -1.0, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_NEAR(output[1][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(t0.adj(), -1.0, 1e-5); +} + +TEST(StanMathOde_ode_adams, ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a); + + output[0][0].grad(); + + EXPECT_NEAR(output[0][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(ts[0].adj(), 0.78070695113, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_NEAR(output[1][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(ts[1].adj(), -0.0791208888, 1e-5); +} + +TEST(StanMathOde_ode_adams, ts_repeat) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 0.45, 1.1, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a); + + EXPECT_EQ(output.size(), ts.size()); + + output[0][0].grad(); + + EXPECT_NEAR(output[0][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(ts[0].adj(), 0.78070695113, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_NEAR(output[1][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(ts[1].adj(), 0.78070695113, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[2][0].grad(); + + EXPECT_NEAR(output[2][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(ts[2].adj(), -0.0791208888, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[3][0].grad(); + + EXPECT_NEAR(output[3][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(ts[3].adj(), -0.0791208888, 1e-5); +} + +TEST(StanMathOde_ode_adams, scalar_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a = 1.5; + + var output = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a.adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_adams, scalar_arg_multi_time) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + var a = 1.5; + + std::vector> output + = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a); + + output[0](0).grad(); + + EXPECT_NEAR(output[0](0).val(), 0.4165982112, 1e-5); + EXPECT_NEAR(a.adj(), -0.04352005542, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1](0).grad(); + + EXPECT_NEAR(output[1](0).val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a.adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_adams, std_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + std::vector a = {1.5}; + + var output = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a[0].adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_adams, vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a(0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_adams, row_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a(0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_adams, matrix_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1, 1); + a << 1.5; + + var output = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a(0, 0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_adams, scalar_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 0.75; + std::vector a1 = {0.75}; + + var output + = stan::math::ode_adams(Cos2Arg(), y0, t0, ts, nullptr, a0, a1)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a0.adj(), -0.50107310888, 1e-5); + EXPECT_NEAR(a1[0].adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_adams, std_vector_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + std::vector a1(1, a0); + std::vector> a2(1, a1); + + var output = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0][0].adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_adams, std_vector_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0](0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_adams, std_vector_row_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0](0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_adams, std_vector_matrix_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1, 1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_adams(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0](0).adj(), -0.50107310888, 1e-5); +} + +struct ayt { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const T_y& y, std::ostream* msgs, const T2& a) const { + return -a * y * t; + } +}; + +TEST(StanMathOde_ode_adams, arg_combos_test) { + var t0 = 0.5; + var a = 0.2; + std::vector ts = {1.25}; + Eigen::Matrix y0(1); + y0 << 0.75; + + double t0d = stan::math::value_of(t0); + double ad = stan::math::value_of(a); + std::vector tsd = stan::math::value_of(ts); + Eigen::VectorXd y0d = stan::math::value_of(y0); + + auto check_yT = [&](auto yT) { + EXPECT_NEAR(stan::math::value_of(yT), + y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), 1e-5); + }; + + auto check_t0 = [&](var t0) { + EXPECT_NEAR( + t0.adj(), + ad * t0d * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + auto check_a = [&](var a) { + EXPECT_NEAR(a.adj(), + -0.5 * (tsd[0] * tsd[0] - t0d * t0d) * y0d(0) + * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + auto check_ts = [&](std::vector ts) { + EXPECT_NEAR( + ts[0].adj(), + -ad * tsd[0] * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + auto check_y0 = [&](Eigen::Matrix y0) { + EXPECT_NEAR(y0(0).adj(), exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + double yT1 = stan::math::ode_adams(ayt(), y0d, t0d, tsd, nullptr, ad)[0](0); + check_yT(yT1); + + var yT2 = stan::math::ode_adams(ayt(), y0d, t0d, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT2.grad(); + check_yT(yT2); + check_a(a); + + var yT3 = stan::math::ode_adams(ayt(), y0d, t0d, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT3.grad(); + check_yT(yT3); + check_ts(ts); + + var yT4 = stan::math::ode_adams(ayt(), y0d, t0d, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT4.grad(); + check_yT(yT4); + check_ts(ts); + check_a(a); + + var yT5 = stan::math::ode_adams(ayt(), y0d, t0, tsd, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT5.grad(); + check_yT(yT5); + check_t0(t0); + + var yT6 = stan::math::ode_adams(ayt(), y0d, t0, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT6.grad(); + check_yT(yT6); + check_t0(t0); + check_a(a); + + var yT7 = stan::math::ode_adams(ayt(), y0d, t0, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT7.grad(); + check_yT(yT7); + check_t0(t0); + check_ts(ts); + + var yT8 = stan::math::ode_adams(ayt(), y0d, t0, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT8.grad(); + check_yT(yT8); + check_t0(t0); + check_ts(ts); + check_a(a); + + var yT9 = stan::math::ode_adams(ayt(), y0, t0d, tsd, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT9.grad(); + check_yT(yT9); + check_y0(y0); + + var yT10 = stan::math::ode_adams(ayt(), y0, t0d, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT10.grad(); + check_yT(yT10); + check_y0(y0); + check_a(a); + + var yT11 = stan::math::ode_adams(ayt(), y0, t0d, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT11.grad(); + check_yT(yT11); + check_y0(y0); + check_ts(ts); + + var yT12 = stan::math::ode_adams(ayt(), y0, t0d, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT12.grad(); + check_yT(yT12); + check_y0(y0); + check_ts(ts); + check_a(a); + + var yT13 = stan::math::ode_adams(ayt(), y0, t0, tsd, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT13.grad(); + check_yT(yT13); + check_y0(y0); + check_t0(t0); + + var yT14 = stan::math::ode_adams(ayt(), y0, t0, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT14.grad(); + check_yT(yT14); + check_y0(y0); + check_t0(t0); + check_a(a); + + var yT15 = stan::math::ode_adams(ayt(), y0, t0, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT15.grad(); + check_yT(yT15); + check_y0(y0); + check_t0(t0); + check_ts(ts); + + var yT16 = stan::math::ode_adams(ayt(), y0, t0, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT16.grad(); + check_yT(yT16); + check_y0(y0); + check_t0(t0); + check_ts(ts); + check_a(a); +} diff --git a/test/unit/math/rev/functor/ode_adams_tol_prim_test.cpp b/test/unit/math/rev/functor/ode_adams_tol_prim_test.cpp new file mode 100644 index 00000000000..b21f7a15142 --- /dev/null +++ b/test/unit/math/rev/functor/ode_adams_tol_prim_test.cpp @@ -0,0 +1,448 @@ +#include +#include +#include +#include +#include + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +struct CosArgWrongSize { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(2); + out << stan::math::cos(sum_(vec) * t), 0; + return out; + } +}; + +TEST(ode_adams_tol_prim, y0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + Eigen::VectorXd y0inf(1); + Eigen::VectorXd y0NaN(1); + Eigen::VectorXd y0_empty; + y0NaN << stan::math::NOT_A_NUMBER; + y0inf << stan::math::INFTY; + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0inf, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0NaN, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0_empty, t0, ts, 1e-10, + 1e-10, 1e6, nullptr, a), + std::invalid_argument); +} + +TEST(ode_adams_tol_prim, t0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + double t0inf = stan::math::INFTY; + double t0NaN = stan::math::NOT_A_NUMBER; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0inf, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0NaN, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_adams_tol_prim, ts_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + std::vector ts_repeat = {0.45, 0.45}; + std::vector ts_lots = {0.45, 0.45, 1.1, 1.1, 2.0}; + std::vector ts_empty = {}; + std::vector ts_early = {-0.45, 0.2}; + std::vector ts_decreasing = {0.45, 0.2}; + std::vector tsinf = {stan::math::INFTY, 1.1}; + std::vector tsNaN = {0.45, stan::math::NOT_A_NUMBER}; + + double a = 1.5; + + std::vector out; + EXPECT_NO_THROW(out = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, + 1e-10, 1e6, nullptr, a)); + EXPECT_EQ(out.size(), ts.size()); + + EXPECT_NO_THROW(out + = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts_repeat, + 1e-10, 1e-10, 1e6, nullptr, a)); + EXPECT_EQ(out.size(), ts_repeat.size()); + EXPECT_MATRIX_FLOAT_EQ(out[0], out[1]); + + EXPECT_NO_THROW(out + = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts_lots, 1e-10, + 1e-10, 1e6, nullptr, a)); + EXPECT_EQ(out.size(), ts_lots.size()); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts_empty, 1e-10, + 1e-10, 1e6, nullptr, a), + std::invalid_argument); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts_early, 1e-10, + 1e-10, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts_decreasing, + 1e-10, 1e-10, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, tsinf, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, tsNaN, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_adams_tol_prim, one_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, va)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, ea)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vva)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vea)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, veaNaN), + std::domain_error); +} + +TEST(ode_adams_tol_prim, two_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, a)); + + EXPECT_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, va)); + + EXPECT_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, ea)); + + EXPECT_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vva)); + + EXPECT_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vea)); + + EXPECT_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, veaNaN), + std::domain_error); +} + +TEST(ode_adams_tol_prim, rtol_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double rtol = 1e-6; + double rtol_negative = -1e-6; + double rtolinf = stan::math::INFTY; + double rtolNaN = stan::math::NOT_A_NUMBER; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, rtol, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, rtol_negative, + 1e-10, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, rtolinf, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, rtolNaN, 1e-10, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_adams_tol_prim, atol_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double atol = 1e-6; + double atol_negative = -1e-6; + double atolinf = stan::math::INFTY; + double atolNaN = stan::math::NOT_A_NUMBER; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, atol, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, + atol_negative, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, atolinf, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, atolNaN, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_adams_tol_prim, max_num_steps_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + int max_num_steps = 500; + int max_num_steps_negative = -500; + int max_num_steps_zero = 0; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + max_num_steps, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + max_num_steps_negative, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + max_num_steps_zero, nullptr, a), + std::domain_error); +} + +TEST(ode_adams_tol_prim, rhs_wrong_size_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, a)); + + EXPECT_THROW(stan::math::ode_adams_tol(CosArgWrongSize(), y0, t0, ts, 1e-6, + 1e-6, 100, nullptr, a), + std::invalid_argument); +} + +TEST(ode_adams_tol_prim, error_name) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double ainf = stan::math::INFTY; + + EXPECT_THROW_MSG(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, ainf), + std::domain_error, "ode_adams_tol"); +} + +TEST(ode_adams_tol_prim, too_much_work) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1e10}; + + double a = 1.0; + + EXPECT_THROW_MSG(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, a), + std::domain_error, + "ode_adams_tol: Failed to integrate to next output time"); +} diff --git a/test/unit/math/rev/functor/ode_adams_tol_rev_test.cpp b/test/unit/math/rev/functor/ode_adams_tol_rev_test.cpp new file mode 100644 index 00000000000..d92be94a14b --- /dev/null +++ b/test/unit/math/rev/functor/ode_adams_tol_rev_test.cpp @@ -0,0 +1,590 @@ +#include +#include +#include +#include +#include + +using stan::math::var; + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +TEST(StanMathOde_ode_adams_tol, int_t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + EXPECT_FLOAT_EQ(output[0][0], 0.4165982112); + EXPECT_FLOAT_EQ(output[1][0], 0.66457668563); +} + +TEST(StanMathOde_ode_adams_tol, int_ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1, 2}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + EXPECT_FLOAT_EQ(output[0][0], 0.6649966577); + EXPECT_FLOAT_EQ(output[1][0], 0.09408000537); +} + +TEST(StanMathOde_ode_adams_tol, t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + var t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + output[0][0].grad(); + + EXPECT_FLOAT_EQ(output[0][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(t0.adj(), -1.0); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_FLOAT_EQ(output[1][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(t0.adj(), -1.0); +} + +TEST(StanMathOde_ode_adams_tol, ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + output[0][0].grad(); + + EXPECT_FLOAT_EQ(output[0][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(ts[0].adj(), 0.78070695113); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_FLOAT_EQ(output[1][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(ts[1].adj(), -0.0791208888); +} + +TEST(StanMathOde_ode_adams_tol, ts_repeat) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 0.45, 1.1, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + EXPECT_EQ(output.size(), ts.size()); + + output[0][0].grad(); + + EXPECT_FLOAT_EQ(output[0][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(ts[0].adj(), 0.78070695113); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_FLOAT_EQ(output[1][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(ts[1].adj(), 0.78070695113); + + stan::math::set_zero_all_adjoints(); + + output[2][0].grad(); + + EXPECT_FLOAT_EQ(output[2][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(ts[2].adj(), -0.0791208888); + + stan::math::set_zero_all_adjoints(); + + output[3][0].grad(); + + EXPECT_FLOAT_EQ(output[3][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(ts[3].adj(), -0.0791208888); +} + +TEST(StanMathOde_ode_adams_tol, scalar_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a = 1.5; + + var output = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, + 1e6, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a.adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_adams_tol, scalar_arg_multi_time) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + var a = 1.5; + + std::vector> output + = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + output[0](0).grad(); + + EXPECT_FLOAT_EQ(output[0](0).val(), 0.4165982112); + EXPECT_FLOAT_EQ(a.adj(), -0.04352005542); + + stan::math::set_zero_all_adjoints(); + + output[1](0).grad(); + + EXPECT_FLOAT_EQ(output[1](0).val(), 0.66457668563); + EXPECT_FLOAT_EQ(a.adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_adams_tol, std_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + std::vector a = {1.5}; + + var output = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, + 1e6, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a[0].adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_adams_tol, vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, + 1e6, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a(0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_adams_tol, row_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, + 1e6, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a(0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_adams_tol, matrix_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1, 1); + a << 1.5; + + var output = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, + 1e6, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a(0, 0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_adams_tol, scalar_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 0.75; + std::vector a1 = {0.75}; + + var output = stan::math::ode_adams_tol(Cos2Arg(), y0, t0, ts, 1e-8, 1e-10, + 1e6, nullptr, a0, a1)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a0.adj(), -0.50107310888); + EXPECT_FLOAT_EQ(a1[0].adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_adams_tol, std_vector_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + std::vector a1(1, a0); + std::vector> a2(1, a1); + + var output = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, + 1e6, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0][0].adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_adams_tol, std_vector_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, + 1e6, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0](0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_adams_tol, std_vector_row_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, + 1e6, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0](0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_adams_tol, std_vector_matrix_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1, 1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, + 1e6, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0](0).adj(), -0.50107310888); +} + +struct ayt { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a) const { + return -a * y * t; + } +}; + +TEST(StanMathOde_ode_adams_tol, arg_combos_test) { + var t0 = 0.5; + var a = 0.2; + std::vector ts = {1.25}; + Eigen::Matrix y0(1); + y0 << 0.75; + + double t0d = stan::math::value_of(t0); + double ad = stan::math::value_of(a); + std::vector tsd = stan::math::value_of(ts); + Eigen::VectorXd y0d = stan::math::value_of(y0); + + auto check_yT = [&](auto yT) { + EXPECT_FLOAT_EQ(stan::math::value_of(yT), + y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_t0 = [&](var t0) { + EXPECT_FLOAT_EQ( + t0.adj(), + ad * t0d * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_a = [&](var a) { + EXPECT_FLOAT_EQ(a.adj(), + -0.5 * (tsd[0] * tsd[0] - t0d * t0d) * y0d(0) + * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_ts = [&](std::vector ts) { + EXPECT_FLOAT_EQ( + ts[0].adj(), + -ad * tsd[0] * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_y0 = [&](Eigen::Matrix y0) { + EXPECT_FLOAT_EQ(y0(0).adj(), + exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + double yT1 = stan::math::ode_adams_tol(ayt(), y0d, t0d, tsd, 1e-10, 1e-10, + 1e6, nullptr, ad)[0](0); + check_yT(yT1); + + var yT2 = stan::math::ode_adams_tol(ayt(), y0d, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT2.grad(); + check_yT(yT2); + check_a(a); + + var yT3 = stan::math::ode_adams_tol(ayt(), y0d, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT3.grad(); + check_yT(yT3); + check_ts(ts); + + var yT4 = stan::math::ode_adams_tol(ayt(), y0d, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT4.grad(); + check_yT(yT4); + check_ts(ts); + check_a(a); + + var yT5 = stan::math::ode_adams_tol(ayt(), y0d, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT5.grad(); + check_yT(yT5); + check_t0(t0); + + var yT6 = stan::math::ode_adams_tol(ayt(), y0d, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT6.grad(); + check_yT(yT6); + check_t0(t0); + check_a(a); + + var yT7 = stan::math::ode_adams_tol(ayt(), y0d, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT7.grad(); + check_yT(yT7); + check_t0(t0); + check_ts(ts); + + var yT8 = stan::math::ode_adams_tol(ayt(), y0d, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT8.grad(); + check_yT(yT8); + check_t0(t0); + check_ts(ts); + check_a(a); + + var yT9 = stan::math::ode_adams_tol(ayt(), y0, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT9.grad(); + check_yT(yT9); + check_y0(y0); + + var yT10 = stan::math::ode_adams_tol(ayt(), y0, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT10.grad(); + check_yT(yT10); + check_y0(y0); + check_a(a); + + var yT11 = stan::math::ode_adams_tol(ayt(), y0, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT11.grad(); + check_yT(yT11); + check_y0(y0); + check_ts(ts); + + var yT12 = stan::math::ode_adams_tol(ayt(), y0, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT12.grad(); + check_yT(yT12); + check_y0(y0); + check_ts(ts); + check_a(a); + + var yT13 = stan::math::ode_adams_tol(ayt(), y0, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT13.grad(); + check_yT(yT13); + check_y0(y0); + check_t0(t0); + + var yT14 = stan::math::ode_adams_tol(ayt(), y0, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT14.grad(); + check_yT(yT14); + check_y0(y0); + check_t0(t0); + check_a(a); + + var yT15 = stan::math::ode_adams_tol(ayt(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT15.grad(); + check_yT(yT15); + check_y0(y0); + check_t0(t0); + check_ts(ts); + + var yT16 = stan::math::ode_adams_tol(ayt(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT16.grad(); + check_yT(yT16); + check_y0(y0); + check_t0(t0); + check_ts(ts); + check_a(a); +} + +TEST(StanMathOde_ode_adams_tol, too_much_work) { + Eigen::Matrix y0 + = Eigen::VectorXd::Zero(1).template cast(); + var t0 = 0; + std::vector ts = {0.45, 1e10}; + + var a = 1.0; + + EXPECT_THROW_MSG(stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, a), + std::domain_error, + "ode_adams_tol: Failed to integrate to next output time"); +} diff --git a/test/unit/math/rev/functor/ode_bdf_prim_test.cpp b/test/unit/math/rev/functor/ode_bdf_prim_test.cpp new file mode 100644 index 00000000000..8afd89965ab --- /dev/null +++ b/test/unit/math/rev/functor/ode_bdf_prim_test.cpp @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +struct CosArgWrongSize { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(2); + out << stan::math::cos(sum_(vec) * t), 0; + return out; + } +}; + +TEST(ode_bdf_prim, y0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + Eigen::VectorXd y0inf(1); + Eigen::VectorXd y0NaN(1); + Eigen::VectorXd y0_empty; + y0NaN << stan::math::NOT_A_NUMBER; + y0inf << stan::math::INFTY; + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0inf, t0, ts, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0NaN, t0, ts, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0_empty, t0, ts, nullptr, a), + std::invalid_argument); +} + +TEST(ode_bdf_prim, t0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + double t0inf = stan::math::INFTY; + double t0NaN = stan::math::NOT_A_NUMBER; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0inf, ts, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0NaN, ts, nullptr, a), + std::domain_error); +} + +TEST(ode_bdf_prim, ts_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + std::vector ts_repeat = {0.45, 0.45}; + std::vector ts_lots = {0.45, 0.45, 1.1, 1.1, 2.0}; + std::vector ts_empty = {}; + std::vector ts_early = {-0.45, 0.2}; + std::vector ts_decreasing = {0.45, 0.2}; + std::vector tsinf = {stan::math::INFTY, 1.1}; + std::vector tsNaN = {0.45, stan::math::NOT_A_NUMBER}; + + double a = 1.5; + + std::vector out; + EXPECT_NO_THROW(out = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a)); + EXPECT_EQ(out.size(), ts.size()); + + EXPECT_NO_THROW( + out = stan::math::ode_bdf(CosArg1(), y0, t0, ts_repeat, nullptr, a)); + EXPECT_EQ(out.size(), ts_repeat.size()); + EXPECT_MATRIX_FLOAT_EQ(out[0], out[1]); + + EXPECT_NO_THROW( + out = stan::math::ode_bdf(CosArg1(), y0, t0, ts_lots, nullptr, a)); + EXPECT_EQ(out.size(), ts_lots.size()); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts_empty, nullptr, a), + std::invalid_argument); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts_early, nullptr, a), + std::domain_error); + + EXPECT_THROW( + stan::math::ode_bdf(CosArg1(), y0, t0, ts_decreasing, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, tsinf, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, tsNaN, nullptr, a), + std::domain_error); +} + +TEST(ode_bdf_prim, one_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, va)); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, ea)); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, vva)); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, vea)); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, veaNaN), + std::domain_error); +} + +TEST(ode_bdf_prim, two_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, a)); + + EXPECT_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, va)); + + EXPECT_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, ea)); + + EXPECT_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, vva)); + + EXPECT_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, vea)); + + EXPECT_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a, veaNaN), + std::domain_error); +} + +TEST(ode_bdf_prim, rhs_wrong_size_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf(CosArgWrongSize(), y0, t0, ts, nullptr, a), + std::invalid_argument); +} + +TEST(ode_bdf_prim, error_name) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double ainf = stan::math::INFTY; + + EXPECT_THROW_MSG(stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, ainf), + std::domain_error, "ode_bdf"); +} diff --git a/test/unit/math/rev/functor/ode_bdf_rev_test.cpp b/test/unit/math/rev/functor/ode_bdf_rev_test.cpp new file mode 100644 index 00000000000..7fc537d4185 --- /dev/null +++ b/test/unit/math/rev/functor/ode_bdf_rev_test.cpp @@ -0,0 +1,547 @@ +#include +#include +#include +#include + +using stan::math::var; + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const T_y& y, std::ostream* msgs, + const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out( + 1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +TEST(StanMathOde_ode_bdf, int_t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a); + + EXPECT_NEAR(output[0][0], 0.4165982112, 1e-5); + EXPECT_NEAR(output[1][0], 0.66457668563, 1e-5); +} + +TEST(StanMathOde_ode_bdf, int_ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1, 2}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a); + + EXPECT_NEAR(output[0][0], 0.6649966577, 1e-5); + EXPECT_NEAR(output[1][0], 0.09408000537, 1e-5); +} + +TEST(StanMathOde_ode_bdf, t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + var t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a); + + output[0][0].grad(); + + EXPECT_NEAR(output[0][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(t0.adj(), -1.0, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_NEAR(output[1][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(t0.adj(), -1.0, 1e-5); +} + +TEST(StanMathOde_ode_bdf, ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a); + + output[0][0].grad(); + + EXPECT_NEAR(output[0][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(ts[0].adj(), 0.78070695113, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_NEAR(output[1][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(ts[1].adj(), -0.0791208888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, ts_repeat) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 0.45, 1.1, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a); + + EXPECT_EQ(output.size(), ts.size()); + + output[0][0].grad(); + + EXPECT_NEAR(output[0][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(ts[0].adj(), 0.78070695113, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_NEAR(output[1][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(ts[1].adj(), 0.78070695113, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[2][0].grad(); + + EXPECT_NEAR(output[2][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(ts[2].adj(), -0.0791208888, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[3][0].grad(); + + EXPECT_NEAR(output[3][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(ts[3].adj(), -0.0791208888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, scalar_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a = 1.5; + + var output = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a.adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, scalar_arg_multi_time) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + var a = 1.5; + + std::vector> output + = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a); + + output[0](0).grad(); + + EXPECT_NEAR(output[0](0).val(), 0.4165982112, 1e-5); + EXPECT_NEAR(a.adj(), -0.04352005542, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1](0).grad(); + + EXPECT_NEAR(output[1](0).val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a.adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, std_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + std::vector a = {1.5}; + + var output = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a[0].adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a(0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, row_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a(0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, matrix_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1, 1); + a << 1.5; + + var output = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a(0, 0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, scalar_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 0.75; + std::vector a1 = {0.75}; + + var output + = stan::math::ode_bdf(Cos2Arg(), y0, t0, ts, nullptr, a0, a1)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a0.adj(), -0.50107310888, 1e-5); + EXPECT_NEAR(a1[0].adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, std_vector_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + std::vector a1(1, a0); + std::vector> a2(1, a1); + + var output = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0][0].adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, std_vector_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0](0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, std_vector_row_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0](0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_bdf, std_vector_matrix_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1, 1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_bdf(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0](0).adj(), -0.50107310888, 1e-5); +} + +struct ayt { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const T_y& y, std::ostream* msgs, const T2& a) const { + return -a * y * t; + } +}; + +TEST(StanMathOde_ode_bdf, arg_combos_test) { + var t0 = 0.5; + var a = 0.2; + std::vector ts = {1.25}; + Eigen::Matrix y0(1); + y0 << 0.75; + + double t0d = stan::math::value_of(t0); + double ad = stan::math::value_of(a); + std::vector tsd = stan::math::value_of(ts); + Eigen::VectorXd y0d = stan::math::value_of(y0); + + auto check_yT = [&](auto yT) { + EXPECT_NEAR(stan::math::value_of(yT), + y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), 1e-5); + }; + + auto check_t0 = [&](var t0) { + EXPECT_NEAR( + t0.adj(), + ad * t0d * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + auto check_a = [&](var a) { + EXPECT_NEAR(a.adj(), + -0.5 * (tsd[0] * tsd[0] - t0d * t0d) * y0d(0) + * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + auto check_ts = [&](std::vector ts) { + EXPECT_NEAR( + ts[0].adj(), + -ad * tsd[0] * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + auto check_y0 = [&](Eigen::Matrix y0) { + EXPECT_NEAR(y0(0).adj(), exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + double yT1 = stan::math::ode_bdf(ayt(), y0d, t0d, tsd, nullptr, ad)[0](0); + check_yT(yT1); + + var yT2 = stan::math::ode_bdf(ayt(), y0d, t0d, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT2.grad(); + check_yT(yT2); + check_a(a); + + var yT3 = stan::math::ode_bdf(ayt(), y0d, t0d, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT3.grad(); + check_yT(yT3); + check_ts(ts); + + var yT4 = stan::math::ode_bdf(ayt(), y0d, t0d, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT4.grad(); + check_yT(yT4); + check_ts(ts); + check_a(a); + + var yT5 = stan::math::ode_bdf(ayt(), y0d, t0, tsd, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT5.grad(); + check_yT(yT5); + check_t0(t0); + + var yT6 = stan::math::ode_bdf(ayt(), y0d, t0, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT6.grad(); + check_yT(yT6); + check_t0(t0); + check_a(a); + + var yT7 = stan::math::ode_bdf(ayt(), y0d, t0, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT7.grad(); + check_yT(yT7); + check_t0(t0); + check_ts(ts); + + var yT8 = stan::math::ode_bdf(ayt(), y0d, t0, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT8.grad(); + check_yT(yT8); + check_t0(t0); + check_ts(ts); + check_a(a); + + var yT9 = stan::math::ode_bdf(ayt(), y0, t0d, tsd, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT9.grad(); + check_yT(yT9); + check_y0(y0); + + var yT10 = stan::math::ode_bdf(ayt(), y0, t0d, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT10.grad(); + check_yT(yT10); + check_y0(y0); + check_a(a); + + var yT11 = stan::math::ode_bdf(ayt(), y0, t0d, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT11.grad(); + check_yT(yT11); + check_y0(y0); + check_ts(ts); + + var yT12 = stan::math::ode_bdf(ayt(), y0, t0d, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT12.grad(); + check_yT(yT12); + check_y0(y0); + check_ts(ts); + check_a(a); + + var yT13 = stan::math::ode_bdf(ayt(), y0, t0, tsd, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT13.grad(); + check_yT(yT13); + check_y0(y0); + check_t0(t0); + + var yT14 = stan::math::ode_bdf(ayt(), y0, t0, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT14.grad(); + check_yT(yT14); + check_y0(y0); + check_t0(t0); + check_a(a); + + var yT15 = stan::math::ode_bdf(ayt(), y0, t0, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT15.grad(); + check_yT(yT15); + check_y0(y0); + check_t0(t0); + check_ts(ts); + + var yT16 = stan::math::ode_bdf(ayt(), y0, t0, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT16.grad(); + check_yT(yT16); + check_y0(y0); + check_t0(t0); + check_ts(ts); + check_a(a); +} diff --git a/test/unit/math/rev/functor/ode_bdf_tol_prim_test.cpp b/test/unit/math/rev/functor/ode_bdf_tol_prim_test.cpp new file mode 100644 index 00000000000..4497e90b773 --- /dev/null +++ b/test/unit/math/rev/functor/ode_bdf_tol_prim_test.cpp @@ -0,0 +1,446 @@ +#include +#include +#include +#include +#include + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +struct CosArgWrongSize { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(2); + out << stan::math::cos(sum_(vec) * t), 0; + return out; + } +}; + +TEST(ode_bdf_tol_prim, y0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + Eigen::VectorXd y0inf(1); + Eigen::VectorXd y0NaN(1); + Eigen::VectorXd y0_empty; + y0NaN << stan::math::NOT_A_NUMBER; + y0inf << stan::math::INFTY; + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0inf, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0NaN, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0_empty, t0, ts, 1e-10, + 1e-10, 1e6, nullptr, a), + std::invalid_argument); +} + +TEST(ode_bdf_tol_prim, t0_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + double t0inf = stan::math::INFTY; + double t0NaN = stan::math::NOT_A_NUMBER; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0inf, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0NaN, ts, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_bdf_tol_prim, ts_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + std::vector ts_repeat = {0.45, 0.45}; + std::vector ts_lots = {0.45, 0.45, 1.1, 1.1, 2.0}; + std::vector ts_empty = {}; + std::vector ts_early = {-0.45, 0.2}; + std::vector ts_decreasing = {0.45, 0.2}; + std::vector tsinf = {stan::math::INFTY, 1.1}; + std::vector tsNaN = {0.45, stan::math::NOT_A_NUMBER}; + + double a = 1.5; + + std::vector out; + EXPECT_NO_THROW(out = stan::math::ode_adams_tol(CosArg1(), y0, t0, ts, 1e-10, + 1e-10, 1e6, nullptr, a)); + EXPECT_EQ(out.size(), ts.size()); + + EXPECT_NO_THROW(out = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts_repeat, + 1e-10, 1e-10, 1e6, nullptr, a)); + EXPECT_EQ(out.size(), ts_repeat.size()); + EXPECT_MATRIX_FLOAT_EQ(out[0], out[1]); + + EXPECT_NO_THROW(out = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts_lots, + 1e-10, 1e-10, 1e6, nullptr, a)); + EXPECT_EQ(out.size(), ts_lots.size()); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts_empty, 1e-10, + 1e-10, 1e6, nullptr, a), + std::invalid_argument); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts_early, 1e-10, + 1e-10, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts_decreasing, 1e-10, + 1e-10, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, tsinf, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, tsNaN, 1e-10, 1e-10, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_bdf_tol_prim, one_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, va)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, ea)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vva)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, vea)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, veaNaN), + std::domain_error); +} + +TEST(ode_bdf_tol_prim, two_arg_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + double ainf = stan::math::INFTY; + double aNaN = stan::math::NOT_A_NUMBER; + + std::vector va = {a}; + std::vector vainf = {ainf}; + std::vector vaNaN = {aNaN}; + + Eigen::VectorXd ea(1); + ea << a; + Eigen::VectorXd eainf(1); + eainf << ainf; + Eigen::VectorXd eaNaN(1); + eaNaN << aNaN; + + std::vector> vva = {va}; + std::vector> vvainf = {vainf}; + std::vector> vvaNaN = {vaNaN}; + + std::vector vea = {ea}; + std::vector veainf = {eainf}; + std::vector veaNaN = {eaNaN}; + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, a)); + + EXPECT_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a, ainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a, aNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, va)); + + EXPECT_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a, vainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a, vaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, ea)); + + EXPECT_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a, eainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a, eaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vva)); + + EXPECT_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a, vvainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a, vvaNaN), + std::domain_error); + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, + 1e6, nullptr, a, vea)); + + EXPECT_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a, veainf), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a, veaNaN), + std::domain_error); +} + +TEST(ode_bdf_tol_prim, rtol_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double rtol = 1e-6; + double rtol_negative = -1e-6; + double rtolinf = stan::math::INFTY; + double rtolNaN = stan::math::NOT_A_NUMBER; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, rtol, 1e-10, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, rtol_negative, + 1e-10, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, rtolinf, 1e-10, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, rtolNaN, 1e-10, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_bdf_tol_prim, atol_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double atol = 1e-6; + double atol_negative = -1e-6; + double atolinf = stan::math::INFTY; + double atolNaN = stan::math::NOT_A_NUMBER; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, atol, + 1e6, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, + atol_negative, 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, atolinf, + 1e6, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, atolNaN, + 1e6, nullptr, a), + std::domain_error); +} + +TEST(ode_bdf_tol_prim, max_num_steps_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + int max_num_steps = 500; + int max_num_steps_negative = -500; + int max_num_steps_zero = 0; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + max_num_steps, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + max_num_steps_negative, nullptr, a), + std::domain_error); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + max_num_steps_zero, nullptr, a), + std::domain_error); +} + +TEST(ode_bdf_tol_prim, rhs_wrong_size_errors) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + EXPECT_NO_THROW(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, a)); + + EXPECT_THROW(stan::math::ode_bdf_tol(CosArgWrongSize(), y0, t0, ts, 1e-6, + 1e-6, 100, nullptr, a), + std::invalid_argument); +} + +TEST(ode_bdf_tol_prim, error_name) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1.1}; + + double ainf = stan::math::INFTY; + + EXPECT_THROW_MSG(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, ainf), + std::domain_error, "ode_bdf_tol"); +} + +TEST(ode_bdf_tol_prim, too_much_work) { + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0; + std::vector ts = {0.45, 1e10}; + + double a = 1.0; + + EXPECT_THROW_MSG(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, a), + std::domain_error, + "ode_bdf_tol: Failed to integrate to next output time"); +} diff --git a/test/unit/math/rev/functor/ode_bdf_tol_rev_test.cpp b/test/unit/math/rev/functor/ode_bdf_tol_rev_test.cpp new file mode 100644 index 00000000000..c1536d3ddd2 --- /dev/null +++ b/test/unit/math/rev/functor/ode_bdf_tol_rev_test.cpp @@ -0,0 +1,590 @@ +#include +#include +#include +#include +#include + +using stan::math::var; + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +TEST(StanMathOde_ode_bdf_tol, int_t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + EXPECT_FLOAT_EQ(output[0][0], 0.4165982112); + EXPECT_FLOAT_EQ(output[1][0], 0.66457668563); +} + +TEST(StanMathOde_ode_bdf_tol, int_ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1, 2}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + EXPECT_FLOAT_EQ(output[0][0], 0.6649966577); + EXPECT_FLOAT_EQ(output[1][0], 0.09408000537); +} + +TEST(StanMathOde_ode_bdf_tol, t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + var t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + output[0][0].grad(); + + EXPECT_FLOAT_EQ(output[0][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(t0.adj(), -1.0); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_FLOAT_EQ(output[1][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(t0.adj(), -1.0); +} + +TEST(StanMathOde_ode_bdf_tol, ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + output[0][0].grad(); + + EXPECT_FLOAT_EQ(output[0][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(ts[0].adj(), 0.78070695113); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_FLOAT_EQ(output[1][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(ts[1].adj(), -0.0791208888); +} + +TEST(StanMathOde_ode_bdf_tol, ts_repeat) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 0.45, 1.1, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + EXPECT_EQ(output.size(), ts.size()); + + output[0][0].grad(); + + EXPECT_FLOAT_EQ(output[0][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(ts[0].adj(), 0.78070695113); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_FLOAT_EQ(output[1][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(ts[1].adj(), 0.78070695113); + + stan::math::set_zero_all_adjoints(); + + output[2][0].grad(); + + EXPECT_FLOAT_EQ(output[2][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(ts[2].adj(), -0.0791208888); + + stan::math::set_zero_all_adjoints(); + + output[3][0].grad(); + + EXPECT_FLOAT_EQ(output[3][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(ts[3].adj(), -0.0791208888); +} + +TEST(StanMathOde_ode_bdf_tol, scalar_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a = 1.5; + + var output = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a.adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_bdf_tol, scalar_arg_multi_time) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + var a = 1.5; + + std::vector> output + = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + output[0](0).grad(); + + EXPECT_FLOAT_EQ(output[0](0).val(), 0.4165982112); + EXPECT_FLOAT_EQ(a.adj(), -0.04352005542); + + stan::math::set_zero_all_adjoints(); + + output[1](0).grad(); + + EXPECT_FLOAT_EQ(output[1](0).val(), 0.66457668563); + EXPECT_FLOAT_EQ(a.adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_bdf_tol, std_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + std::vector a = {1.5}; + + var output = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a[0].adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_bdf_tol, vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a(0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_bdf_tol, row_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a(0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_bdf_tol, matrix_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1, 1); + a << 1.5; + + var output = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a(0, 0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_bdf_tol, scalar_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 0.75; + std::vector a1 = {0.75}; + + var output = stan::math::ode_bdf_tol(Cos2Arg(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a0, a1)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a0.adj(), -0.50107310888); + EXPECT_FLOAT_EQ(a1[0].adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_bdf_tol, std_vector_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + std::vector a1(1, a0); + std::vector> a2(1, a1); + + var output = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0][0].adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_bdf_tol, std_vector_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0](0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_bdf_tol, std_vector_row_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0](0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_bdf_tol, std_vector_matrix_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1, 1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0](0).adj(), -0.50107310888); +} + +struct ayt { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a) const { + return -a * y * t; + } +}; + +TEST(StanMathOde_ode_bdf_tol, arg_combos_test) { + var t0 = 0.5; + var a = 0.2; + std::vector ts = {1.25}; + Eigen::Matrix y0(1); + y0 << 0.75; + + double t0d = stan::math::value_of(t0); + double ad = stan::math::value_of(a); + std::vector tsd = stan::math::value_of(ts); + Eigen::VectorXd y0d = stan::math::value_of(y0); + + auto check_yT = [&](auto yT) { + EXPECT_FLOAT_EQ(stan::math::value_of(yT), + y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_t0 = [&](var t0) { + EXPECT_FLOAT_EQ( + t0.adj(), + ad * t0d * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_a = [&](var a) { + EXPECT_FLOAT_EQ(a.adj(), + -0.5 * (tsd[0] * tsd[0] - t0d * t0d) * y0d(0) + * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_ts = [&](std::vector ts) { + EXPECT_FLOAT_EQ( + ts[0].adj(), + -ad * tsd[0] * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_y0 = [&](Eigen::Matrix y0) { + EXPECT_FLOAT_EQ(y0(0).adj(), + exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + double yT1 = stan::math::ode_bdf_tol(ayt(), y0d, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + check_yT(yT1); + + var yT2 = stan::math::ode_bdf_tol(ayt(), y0d, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT2.grad(); + check_yT(yT2); + check_a(a); + + var yT3 = stan::math::ode_bdf_tol(ayt(), y0d, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT3.grad(); + check_yT(yT3); + check_ts(ts); + + var yT4 = stan::math::ode_bdf_tol(ayt(), y0d, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT4.grad(); + check_yT(yT4); + check_ts(ts); + check_a(a); + + var yT5 = stan::math::ode_bdf_tol(ayt(), y0d, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT5.grad(); + check_yT(yT5); + check_t0(t0); + + var yT6 = stan::math::ode_bdf_tol(ayt(), y0d, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT6.grad(); + check_yT(yT6); + check_t0(t0); + check_a(a); + + var yT7 = stan::math::ode_bdf_tol(ayt(), y0d, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT7.grad(); + check_yT(yT7); + check_t0(t0); + check_ts(ts); + + var yT8 = stan::math::ode_bdf_tol(ayt(), y0d, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT8.grad(); + check_yT(yT8); + check_t0(t0); + check_ts(ts); + check_a(a); + + var yT9 = stan::math::ode_bdf_tol(ayt(), y0, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT9.grad(); + check_yT(yT9); + check_y0(y0); + + var yT10 = stan::math::ode_bdf_tol(ayt(), y0, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT10.grad(); + check_yT(yT10); + check_y0(y0); + check_a(a); + + var yT11 = stan::math::ode_bdf_tol(ayt(), y0, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT11.grad(); + check_yT(yT11); + check_y0(y0); + check_ts(ts); + + var yT12 = stan::math::ode_bdf_tol(ayt(), y0, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT12.grad(); + check_yT(yT12); + check_y0(y0); + check_ts(ts); + check_a(a); + + var yT13 = stan::math::ode_bdf_tol(ayt(), y0, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT13.grad(); + check_yT(yT13); + check_y0(y0); + check_t0(t0); + + var yT14 = stan::math::ode_bdf_tol(ayt(), y0, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT14.grad(); + check_yT(yT14); + check_y0(y0); + check_t0(t0); + check_a(a); + + var yT15 = stan::math::ode_bdf_tol(ayt(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT15.grad(); + check_yT(yT15); + check_y0(y0); + check_t0(t0); + check_ts(ts); + + var yT16 = stan::math::ode_bdf_tol(ayt(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT16.grad(); + check_yT(yT16); + check_y0(y0); + check_t0(t0); + check_ts(ts); + check_a(a); +} + +TEST(StanMathOde_ode_bdf_tol, too_much_work) { + Eigen::Matrix y0 + = Eigen::VectorXd::Zero(1).template cast(); + var t0 = 0; + std::vector ts = {0.45, 1e10}; + + var a = 1.0; + + EXPECT_THROW_MSG(stan::math::ode_bdf_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, a), + std::domain_error, + "ode_bdf_tol: Failed to integrate to next output time"); +} diff --git a/test/unit/math/rev/functor/ode_rk45_rev_test.cpp b/test/unit/math/rev/functor/ode_rk45_rev_test.cpp new file mode 100644 index 00000000000..40291d726f3 --- /dev/null +++ b/test/unit/math/rev/functor/ode_rk45_rev_test.cpp @@ -0,0 +1,547 @@ +#include +#include +#include +#include + +using stan::math::var; + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const T_y& y, std::ostream* msgs, + const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out( + 1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +TEST(StanMathOde_ode_rk45, int_t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a); + + EXPECT_NEAR(output[0][0], 0.4165982112, 1e-5); + EXPECT_NEAR(output[1][0], 0.66457668563, 1e-5); +} + +TEST(StanMathOde_ode_rk45, int_ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1, 2}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a); + + EXPECT_NEAR(output[0][0], 0.6649966577, 1e-5); + EXPECT_NEAR(output[1][0], 0.09408000537, 1e-5); +} + +TEST(StanMathOde_ode_rk45, t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + var t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a); + + output[0][0].grad(); + + EXPECT_NEAR(output[0][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(t0.adj(), -1.0, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_NEAR(output[1][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(t0.adj(), -1.0, 1e-5); +} + +TEST(StanMathOde_ode_rk45, ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a); + + output[0][0].grad(); + + EXPECT_NEAR(output[0][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(ts[0].adj(), 0.78070695113, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_NEAR(output[1][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(ts[1].adj(), -0.0791208888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, ts_repeat) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 0.45, 1.1, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a); + + EXPECT_EQ(output.size(), ts.size()); + + output[0][0].grad(); + + EXPECT_NEAR(output[0][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(ts[0].adj(), 0.78070695113, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_NEAR(output[1][0].val(), 0.4165982112, 1e-5); + EXPECT_NEAR(ts[1].adj(), 0.78070695113, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[2][0].grad(); + + EXPECT_NEAR(output[2][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(ts[2].adj(), -0.0791208888, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[3][0].grad(); + + EXPECT_NEAR(output[3][0].val(), 0.66457668563, 1e-5); + EXPECT_NEAR(ts[3].adj(), -0.0791208888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, scalar_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a = 1.5; + + var output = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a.adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, scalar_arg_multi_time) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + var a = 1.5; + + std::vector> output + = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a); + + output[0](0).grad(); + + EXPECT_NEAR(output[0](0).val(), 0.4165982112, 1e-5); + EXPECT_NEAR(a.adj(), -0.04352005542, 1e-5); + + stan::math::set_zero_all_adjoints(); + + output[1](0).grad(); + + EXPECT_NEAR(output[1](0).val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a.adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, std_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + std::vector a = {1.5}; + + var output = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a[0].adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a(0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, row_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a(0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, matrix_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1, 1); + a << 1.5; + + var output = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a(0, 0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, scalar_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 0.75; + std::vector a1 = {0.75}; + + var output + = stan::math::ode_rk45(Cos2Arg(), y0, t0, ts, nullptr, a0, a1)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a0.adj(), -0.50107310888, 1e-5); + EXPECT_NEAR(a1[0].adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, std_vector_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + std::vector a1(1, a0); + std::vector> a2(1, a1); + + var output = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0][0].adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, std_vector_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0](0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, std_vector_row_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0](0).adj(), -0.50107310888, 1e-5); +} + +TEST(StanMathOde_ode_rk45, std_vector_matrix_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1, 1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_rk45(CosArg1(), y0, t0, ts, nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_NEAR(output.val(), 0.66457668563, 1e-5); + EXPECT_NEAR(a2[0](0).adj(), -0.50107310888, 1e-5); +} + +struct ayt { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const T_y& y, std::ostream* msgs, const T2& a) const { + return -a * y * t; + } +}; + +TEST(StanMathOde_ode_rk45, arg_combos_test) { + var t0 = 0.5; + var a = 0.2; + std::vector ts = {1.25}; + Eigen::Matrix y0(1); + y0 << 0.75; + + double t0d = stan::math::value_of(t0); + double ad = stan::math::value_of(a); + std::vector tsd = stan::math::value_of(ts); + Eigen::VectorXd y0d = stan::math::value_of(y0); + + auto check_yT = [&](auto yT) { + EXPECT_NEAR(stan::math::value_of(yT), + y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), 1e-5); + }; + + auto check_t0 = [&](var t0) { + EXPECT_NEAR( + t0.adj(), + ad * t0d * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + auto check_a = [&](var a) { + EXPECT_NEAR(a.adj(), + -0.5 * (tsd[0] * tsd[0] - t0d * t0d) * y0d(0) + * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + auto check_ts = [&](std::vector ts) { + EXPECT_NEAR( + ts[0].adj(), + -ad * tsd[0] * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + auto check_y0 = [&](Eigen::Matrix y0) { + EXPECT_NEAR(y0(0).adj(), exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d)), + 1e-5); + }; + + double yT1 = stan::math::ode_rk45(ayt(), y0d, t0d, tsd, nullptr, ad)[0](0); + check_yT(yT1); + + var yT2 = stan::math::ode_rk45(ayt(), y0d, t0d, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT2.grad(); + check_yT(yT2); + check_a(a); + + var yT3 = stan::math::ode_rk45(ayt(), y0d, t0d, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT3.grad(); + check_yT(yT3); + check_ts(ts); + + var yT4 = stan::math::ode_rk45(ayt(), y0d, t0d, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT4.grad(); + check_yT(yT4); + check_ts(ts); + check_a(a); + + var yT5 = stan::math::ode_rk45(ayt(), y0d, t0, tsd, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT5.grad(); + check_yT(yT5); + check_t0(t0); + + var yT6 = stan::math::ode_rk45(ayt(), y0d, t0, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT6.grad(); + check_yT(yT6); + check_t0(t0); + check_a(a); + + var yT7 = stan::math::ode_rk45(ayt(), y0d, t0, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT7.grad(); + check_yT(yT7); + check_t0(t0); + check_ts(ts); + + var yT8 = stan::math::ode_rk45(ayt(), y0d, t0, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT8.grad(); + check_yT(yT8); + check_t0(t0); + check_ts(ts); + check_a(a); + + var yT9 = stan::math::ode_rk45(ayt(), y0, t0d, tsd, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT9.grad(); + check_yT(yT9); + check_y0(y0); + + var yT10 = stan::math::ode_rk45(ayt(), y0, t0d, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT10.grad(); + check_yT(yT10); + check_y0(y0); + check_a(a); + + var yT11 = stan::math::ode_rk45(ayt(), y0, t0d, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT11.grad(); + check_yT(yT11); + check_y0(y0); + check_ts(ts); + + var yT12 = stan::math::ode_rk45(ayt(), y0, t0d, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT12.grad(); + check_yT(yT12); + check_y0(y0); + check_ts(ts); + check_a(a); + + var yT13 = stan::math::ode_rk45(ayt(), y0, t0, tsd, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT13.grad(); + check_yT(yT13); + check_y0(y0); + check_t0(t0); + + var yT14 = stan::math::ode_rk45(ayt(), y0, t0, tsd, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT14.grad(); + check_yT(yT14); + check_y0(y0); + check_t0(t0); + check_a(a); + + var yT15 = stan::math::ode_rk45(ayt(), y0, t0, ts, nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT15.grad(); + check_yT(yT15); + check_y0(y0); + check_t0(t0); + check_ts(ts); + + var yT16 = stan::math::ode_rk45(ayt(), y0, t0, ts, nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT16.grad(); + check_yT(yT16); + check_y0(y0); + check_t0(t0); + check_ts(ts); + check_a(a); +} diff --git a/test/unit/math/rev/functor/ode_rk45_tol_rev_test.cpp b/test/unit/math/rev/functor/ode_rk45_tol_rev_test.cpp new file mode 100644 index 00000000000..7db3e1e2aee --- /dev/null +++ b/test/unit/math/rev/functor/ode_rk45_tol_rev_test.cpp @@ -0,0 +1,590 @@ +#include +#include +#include +#include +#include + +using stan::math::var; + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct CosArg1 { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T_Args&... a) const { + std::vector::type> vec + = {sum_(a)...}; + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos(sum_(vec) * t); + return out; + } +}; + +struct Cos2Arg { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a, const T3& b) const { + Eigen::Matrix, Eigen::Dynamic, 1> out(1); + out << stan::math::cos((sum_(a) + sum_(b)) * t); + return out; + } +}; + +TEST(StanMathOde_ode_rk45_tol, int_t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + int t0 = 0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + EXPECT_FLOAT_EQ(output[0][0], 0.4165982112); + EXPECT_FLOAT_EQ(output[1][0], 0.66457668563); +} + +TEST(StanMathOde_ode_rk45_tol, int_ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1, 2}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + EXPECT_FLOAT_EQ(output[0][0], 0.6649966577); + EXPECT_FLOAT_EQ(output[1][0], 0.09408000537); +} + +TEST(StanMathOde_ode_rk45_tol, t0) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + var t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + output[0][0].grad(); + + EXPECT_FLOAT_EQ(output[0][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(t0.adj(), -1.0); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_FLOAT_EQ(output[1][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(t0.adj(), -1.0); +} + +TEST(StanMathOde_ode_rk45_tol, ts) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + output[0][0].grad(); + + EXPECT_FLOAT_EQ(output[0][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(ts[0].adj(), 0.78070695113); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_FLOAT_EQ(output[1][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(ts[1].adj(), -0.0791208888); +} + +TEST(StanMathOde_ode_rk45_tol, ts_repeat) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 0.45, 1.1, 1.1}; + + double a = 1.5; + + std::vector> output + = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + EXPECT_EQ(output.size(), ts.size()); + + output[0][0].grad(); + + EXPECT_FLOAT_EQ(output[0][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(ts[0].adj(), 0.78070695113); + + stan::math::set_zero_all_adjoints(); + + output[1][0].grad(); + + EXPECT_FLOAT_EQ(output[1][0].val(), 0.4165982112); + EXPECT_FLOAT_EQ(ts[1].adj(), 0.78070695113); + + stan::math::set_zero_all_adjoints(); + + output[2][0].grad(); + + EXPECT_FLOAT_EQ(output[2][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(ts[2].adj(), -0.0791208888); + + stan::math::set_zero_all_adjoints(); + + output[3][0].grad(); + + EXPECT_FLOAT_EQ(output[3][0].val(), 0.66457668563); + EXPECT_FLOAT_EQ(ts[3].adj(), -0.0791208888); +} + +TEST(StanMathOde_ode_rk45_tol, scalar_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a = 1.5; + + var output = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a.adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_rk45_tol, scalar_arg_multi_time) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {0.45, 1.1}; + + var a = 1.5; + + std::vector> output + = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a); + + output[0](0).grad(); + + EXPECT_FLOAT_EQ(output[0](0).val(), 0.4165982112); + EXPECT_FLOAT_EQ(a.adj(), -0.04352005542); + + stan::math::set_zero_all_adjoints(); + + output[1](0).grad(); + + EXPECT_FLOAT_EQ(output[1](0).val(), 0.66457668563); + EXPECT_FLOAT_EQ(a.adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_rk45_tol, std_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + std::vector a = {1.5}; + + var output = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a[0].adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_rk45_tol, vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a(0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_rk45_tol, row_vector_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1); + a << 1.5; + + var output = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a(0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_rk45_tol, matrix_arg) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + Eigen::Matrix a(1, 1); + a << 1.5; + + var output = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a(0, 0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_rk45_tol, scalar_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 0.75; + std::vector a1 = {0.75}; + + var output = stan::math::ode_rk45_tol(Cos2Arg(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a0, a1)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a0.adj(), -0.50107310888); + EXPECT_FLOAT_EQ(a1[0].adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_rk45_tol, std_vector_std_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + std::vector a1(1, a0); + std::vector> a2(1, a1); + + var output = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0][0].adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_rk45_tol, std_vector_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0](0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_rk45_tol, std_vector_row_vector_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0](0).adj(), -0.50107310888); +} + +TEST(StanMathOde_ode_rk45_tol, std_vector_matrix_args) { + using stan::math::var; + + Eigen::VectorXd y0 = Eigen::VectorXd::Zero(1); + double t0 = 0.0; + std::vector ts = {1.1}; + + var a0 = 1.5; + Eigen::Matrix a1(1, 1); + a1 << a0; + std::vector> a2(1, a1); + + var output = stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-8, 1e-10, 1e6, + nullptr, a2)[0][0]; + + output.grad(); + + EXPECT_FLOAT_EQ(output.val(), 0.66457668563); + EXPECT_FLOAT_EQ(a2[0](0).adj(), -0.50107310888); +} + +struct ayt { + template + inline Eigen::Matrix, Eigen::Dynamic, 1> + operator()(const T0& t, const Eigen::Matrix& y, + std::ostream* msgs, const T2& a) const { + return -a * y * t; + } +}; + +TEST(StanMathOde_ode_rk45_tol, arg_combos_test) { + var t0 = 0.5; + var a = 0.2; + std::vector ts = {1.25}; + Eigen::Matrix y0(1); + y0 << 0.75; + + double t0d = stan::math::value_of(t0); + double ad = stan::math::value_of(a); + std::vector tsd = stan::math::value_of(ts); + Eigen::VectorXd y0d = stan::math::value_of(y0); + + auto check_yT = [&](auto yT) { + EXPECT_FLOAT_EQ(stan::math::value_of(yT), + y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_t0 = [&](var t0) { + EXPECT_FLOAT_EQ( + t0.adj(), + ad * t0d * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_a = [&](var a) { + EXPECT_FLOAT_EQ(a.adj(), + -0.5 * (tsd[0] * tsd[0] - t0d * t0d) * y0d(0) + * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_ts = [&](std::vector ts) { + EXPECT_FLOAT_EQ( + ts[0].adj(), + -ad * tsd[0] * y0d(0) * exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + auto check_y0 = [&](Eigen::Matrix y0) { + EXPECT_FLOAT_EQ(y0(0).adj(), + exp(-0.5 * ad * (tsd[0] * tsd[0] - t0d * t0d))); + }; + + double yT1 = stan::math::ode_rk45_tol(ayt(), y0d, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + check_yT(yT1); + + var yT2 = stan::math::ode_rk45_tol(ayt(), y0d, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT2.grad(); + check_yT(yT2); + check_a(a); + + var yT3 = stan::math::ode_rk45_tol(ayt(), y0d, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT3.grad(); + check_yT(yT3); + check_ts(ts); + + var yT4 = stan::math::ode_rk45_tol(ayt(), y0d, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT4.grad(); + check_yT(yT4); + check_ts(ts); + check_a(a); + + var yT5 = stan::math::ode_rk45_tol(ayt(), y0d, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT5.grad(); + check_yT(yT5); + check_t0(t0); + + var yT6 = stan::math::ode_rk45_tol(ayt(), y0d, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT6.grad(); + check_yT(yT6); + check_t0(t0); + check_a(a); + + var yT7 = stan::math::ode_rk45_tol(ayt(), y0d, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT7.grad(); + check_yT(yT7); + check_t0(t0); + check_ts(ts); + + var yT8 = stan::math::ode_rk45_tol(ayt(), y0d, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT8.grad(); + check_yT(yT8); + check_t0(t0); + check_ts(ts); + check_a(a); + + var yT9 = stan::math::ode_rk45_tol(ayt(), y0, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT9.grad(); + check_yT(yT9); + check_y0(y0); + + var yT10 = stan::math::ode_rk45_tol(ayt(), y0, t0d, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT10.grad(); + check_yT(yT10); + check_y0(y0); + check_a(a); + + var yT11 = stan::math::ode_rk45_tol(ayt(), y0, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT11.grad(); + check_yT(yT11); + check_y0(y0); + check_ts(ts); + + var yT12 = stan::math::ode_rk45_tol(ayt(), y0, t0d, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT12.grad(); + check_yT(yT12); + check_y0(y0); + check_ts(ts); + check_a(a); + + var yT13 = stan::math::ode_rk45_tol(ayt(), y0, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT13.grad(); + check_yT(yT13); + check_y0(y0); + check_t0(t0); + + var yT14 = stan::math::ode_rk45_tol(ayt(), y0, t0, tsd, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT14.grad(); + check_yT(yT14); + check_y0(y0); + check_t0(t0); + check_a(a); + + var yT15 = stan::math::ode_rk45_tol(ayt(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, ad)[0](0); + stan::math::set_zero_all_adjoints(); + yT15.grad(); + check_yT(yT15); + check_y0(y0); + check_t0(t0); + check_ts(ts); + + var yT16 = stan::math::ode_rk45_tol(ayt(), y0, t0, ts, 1e-10, 1e-10, 1e6, + nullptr, a)[0](0); + stan::math::set_zero_all_adjoints(); + yT16.grad(); + check_yT(yT16); + check_y0(y0); + check_t0(t0); + check_ts(ts); + check_a(a); +} + +TEST(StanMathOde_ode_rk45_tol, too_much_work) { + Eigen::Matrix y0 + = Eigen::VectorXd::Zero(1).template cast(); + var t0 = 0; + std::vector ts = {0.45, 1e10}; + + var a = 1.0; + + EXPECT_THROW_MSG(stan::math::ode_rk45_tol(CosArg1(), y0, t0, ts, 1e-6, 1e-6, + 100, nullptr, a), + std::domain_error, + "ode_rk45_tol: Failed to integrate to next output time"); +} diff --git a/test/unit/math/rev/functor/ode_store_sensitivities_test.cpp b/test/unit/math/rev/functor/ode_store_sensitivities_test.cpp new file mode 100644 index 00000000000..b49028f03e9 --- /dev/null +++ b/test/unit/math/rev/functor/ode_store_sensitivities_test.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +template * = nullptr> +T sum_(T arg) { + return arg; +} + +template * = nullptr> +auto sum_(EigMat&& arg) { + return stan::math::sum(arg); +} + +template * = nullptr> +auto sum_(Vec&& arg) { + stan::scalar_type_t sum = 0; + for (size_t i = 0; i < arg.size(); ++i) { + sum += sum_(arg[i]); + } + return sum; +} + +struct ayt { + template + inline auto operator()(const T0& t, const T_y& y, std::ostream* msgs, + const T_Args&... args) const { + std::vector::type> vec + = {sum_(args)...}; + Eigen::Matrix, Eigen::Dynamic, 1> + out(2); + out(0) = -sum_(vec) * y(0) * t; + out(1) = -1.7 * sum_(vec) * y(1) * t; + return out; + } +}; + +struct aytm { + template + inline auto operator()(const T0& t, const T_y& y, std::ostream* msgs, + const Eigen::Matrix& args) const { + std::vector::type> vec = {sum_(args)}; + Eigen::Matrix, Eigen::Dynamic, 1> out( + 2); + out(0) = -sum_(vec) * y(0) * t; + out(1) = -1.7 * sum_(vec) * y(1) * t; + return out; + } +}; + +TEST(AgradRev, ode_store_sensitivities) { + using stan::math::coupled_ode_system; + using stan::math::var; + + Eigen::VectorXd y0(2); + y0 << 0.1, 0.2; + Eigen::Matrix y0v = y0.template cast(); + + double a = 1.3; + var av = a; + + ayt func; + + double t0 = 0.5; + double t = 0.75; + + var t0v = t0; + var tv = t; + + std::vector coupled_state = {-0.0975, -0.3315, -3.12000, -5.46975, + -4.29000, -7.45875, -2.1225, -3.9015}; + + auto output = stan::math::ode_store_sensitivities(func, coupled_state, y0v, + t0v, tv, nullptr, av); + + output(0).grad(); + + EXPECT_FLOAT_EQ(output(0).val(), -0.0975); + EXPECT_FLOAT_EQ(y0v(0).adj(), -3.12); + EXPECT_FLOAT_EQ(y0v(1).adj(), -4.29); + EXPECT_FLOAT_EQ(av.adj(), -2.1225); + EXPECT_FLOAT_EQ(t0v.adj(), -1.15089); + EXPECT_FLOAT_EQ(tv.adj(), 0.0950625); + + stan::math::set_zero_all_adjoints(); + + output(1).grad(); + + EXPECT_FLOAT_EQ(output(1).val(), -0.3315); + EXPECT_FLOAT_EQ(y0v(0).adj(), -5.46975); + EXPECT_FLOAT_EQ(y0v(1).adj(), -7.45875); + EXPECT_FLOAT_EQ(av.adj(), -3.9015); + EXPECT_FLOAT_EQ(t0v.adj(), -2.003918); + EXPECT_FLOAT_EQ(tv.adj(), 0.5494613); + + stan::math::recover_memory(); +} + +TEST(AgradRev, ode_store_sensitivities_matrix) { + using stan::math::coupled_ode_system; + using stan::math::var; + + Eigen::VectorXd y0(2); + y0 << 0.1, 0.2; + Eigen::Matrix y0v = y0.template cast(); + + double a = 1.3; + Eigen::Matrix av(1, 1); + av(0, 0) = a; + + aytm func; + + double t0 = 0.5; + double t = 0.75; + + var t0v = t0; + var tv = t; + + std::vector coupled_state = {-0.0975, -0.3315, -3.12000, -5.46975, + -4.29000, -7.45875, -2.1225, -3.9015}; + + auto output = stan::math::ode_store_sensitivities(func, coupled_state, y0v, + t0v, tv, nullptr, av); + + output(0).grad(); + + EXPECT_FLOAT_EQ(output(0).val(), -0.0975); + EXPECT_FLOAT_EQ(y0v(0).adj(), -3.12); + EXPECT_FLOAT_EQ(y0v(1).adj(), -4.29); + EXPECT_FLOAT_EQ(av(0, 0).adj(), -2.1225); + EXPECT_FLOAT_EQ(t0v.adj(), -1.15089); + EXPECT_FLOAT_EQ(tv.adj(), 0.0950625); + + stan::math::set_zero_all_adjoints(); + + output(1).grad(); + + EXPECT_FLOAT_EQ(output(1).val(), -0.3315); + EXPECT_FLOAT_EQ(y0v(0).adj(), -5.46975); + EXPECT_FLOAT_EQ(y0v(1).adj(), -7.45875); + EXPECT_FLOAT_EQ(av(0, 0).adj(), -3.9015); + EXPECT_FLOAT_EQ(t0v.adj(), -2.003918); + EXPECT_FLOAT_EQ(tv.adj(), 0.5494613); + + stan::math::recover_memory(); +} diff --git a/test/unit/math/rev/functor/util_cvodes_adams.hpp b/test/unit/math/rev/functor/util_cvodes_adams.hpp index edb79e21a3e..14622ea9bb6 100644 --- a/test/unit/math/rev/functor/util_cvodes_adams.hpp +++ b/test/unit/math/rev/functor/util_cvodes_adams.hpp @@ -274,7 +274,7 @@ void test_ode_error_conditions(F& f, const double& t0, ts_bad.push_back(1); EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts_bad, theta, x, x_int, &msgs, 1e-8, 1e-10, 1e6), - std::domain_error, "times is not a valid ordered vector"); + std::domain_error, "times is not a valid sorted vector"); EXPECT_EQ("", msgs.str()); msgs.clear(); @@ -357,7 +357,7 @@ void test_ode_error_conditions_nan(F& f, const double& t0, theta_bad[0] = nan; EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta_bad, x, x_int, &msgs, 1e-8, 1e-10, 1e6), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta_bad, x, x_int, &msgs, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_nan.str()); @@ -369,7 +369,7 @@ void test_ode_error_conditions_nan(F& f, const double& t0, x_bad[0] = nan; EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta, x_bad, x_int, &msgs, 1e-8, 1e-10, 1e6), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta, x_bad, x_int, &msgs, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_nan.str()); @@ -453,12 +453,12 @@ void test_ode_error_conditions_inf(F& f, const double& t0, std::vector theta_bad = theta; theta_bad[0] = inf; EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta_bad, x, x_int), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta_bad, x, x_int), std::domain_error, expected_is_inf.str()); theta_bad[0] = -inf; EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta_bad, x, x_int), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta_bad, x, x_int), std::domain_error, expected_is_neg_inf.str()); EXPECT_EQ("", msgs.str()); @@ -468,12 +468,12 @@ void test_ode_error_conditions_inf(F& f, const double& t0, std::vector x_bad = x; x_bad[0] = inf; EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta, x_bad, x_int), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta, x_bad, x_int), std::domain_error, expected_is_inf.str()); x_bad[0] = -inf; EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta, x_bad, x_int), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_adams(f, y0, t0, ts, theta, x_bad, x_int), std::domain_error, expected_is_neg_inf.str()); EXPECT_EQ("", msgs.str()); diff --git a/test/unit/math/rev/functor/util_cvodes_bdf.hpp b/test/unit/math/rev/functor/util_cvodes_bdf.hpp index cc11a004a81..fb97b1ff0e0 100644 --- a/test/unit/math/rev/functor/util_cvodes_bdf.hpp +++ b/test/unit/math/rev/functor/util_cvodes_bdf.hpp @@ -118,6 +118,8 @@ void test_ode_finite_diff_dv(const F& f, const double& t_in, stan::math::set_zero_all_adjoints(); } } + + stan::math::recover_memory(); } // test integrate_ode with initial positions as vars and parameters as doubles @@ -161,6 +163,8 @@ void test_ode_finite_diff_vd(const F& f, const double& t_in, stan::math::set_zero_all_adjoints(); } } + + stan::math::recover_memory(); } // test integrate_ode with initial positions as vars and parameters as vars @@ -227,6 +231,8 @@ void test_ode_finite_diff_vv(const F& f, const double& t_in, stan::math::set_zero_all_adjoints(); } } + + stan::math::recover_memory(); } template @@ -272,7 +278,7 @@ void test_ode_error_conditions(F& f, const double& t0, ts_bad.push_back(1); EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts_bad, theta, x, x_int, &msgs, 1e-8, 1e-10, 1e6), - std::domain_error, "times is not a valid ordered vector"); + std::domain_error, "times is not a valid sorted vector"); EXPECT_EQ("", msgs.str()); msgs.clear(); @@ -355,7 +361,7 @@ void test_ode_error_conditions_nan(F& f, const double& t0, theta_bad[0] = nan; EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta_bad, x, x_int, &msgs, 1e-8, 1e-10, 1e6), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta_bad, x, x_int, &msgs, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_nan.str()); @@ -367,7 +373,7 @@ void test_ode_error_conditions_nan(F& f, const double& t0, x_bad[0] = nan; EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta, x_bad, x_int, &msgs, 1e-8, 1e-10, 1e6), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta, x_bad, x_int, &msgs, 1e-8, 1e-10, 1e6), std::domain_error, expected_is_nan.str()); @@ -451,12 +457,12 @@ void test_ode_error_conditions_inf(F& f, const double& t0, std::vector theta_bad = theta; theta_bad[0] = inf; EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta_bad, x, x_int), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta_bad, x, x_int), std::domain_error, expected_is_inf.str()); theta_bad[0] = -inf; EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta_bad, x, x_int), - std::domain_error, "parameter vector"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta_bad, x, x_int), std::domain_error, expected_is_neg_inf.str()); EXPECT_EQ("", msgs.str()); @@ -466,12 +472,12 @@ void test_ode_error_conditions_inf(F& f, const double& t0, std::vector x_bad = x; x_bad[0] = inf; EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta, x_bad, x_int), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta, x_bad, x_int), std::domain_error, expected_is_inf.str()); x_bad[0] = -inf; EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta, x_bad, x_int), - std::domain_error, "continuous data"); + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG(integrate_ode_bdf(f, y0, t0, ts, theta, x_bad, x_int), std::domain_error, expected_is_neg_inf.str()); EXPECT_EQ("", msgs.str()); diff --git a/test/unit/math/rev/functor/util.hpp b/test/unit/math/rev/functor/util_rk45.hpp similarity index 80% rename from test/unit/math/rev/functor/util.hpp rename to test/unit/math/rev/functor/util_rk45.hpp index b24249de41a..23a73de66b0 100644 --- a/test/unit/math/rev/functor/util.hpp +++ b/test/unit/math/rev/functor/util_rk45.hpp @@ -8,7 +8,7 @@ // calculates finite diffs for integrate_ode_rk45 with varying parameters template -std::vector > finite_diff_params( +std::vector> finite_diff_params( const F& f, const double& t_in, const std::vector& ts, const std::vector& y_in, const std::vector& theta, const std::vector& x, const std::vector& x_int, @@ -26,15 +26,15 @@ std::vector > finite_diff_params( } } - std::vector > ode_res_ub; - std::vector > ode_res_lb; + std::vector> ode_res_ub; + std::vector> ode_res_lb; ode_res_ub = stan::math::integrate_ode_rk45(f, y_in, t_in, ts, theta_ub, x, x_int, &msgs); ode_res_lb = stan::math::integrate_ode_rk45(f, y_in, t_in, ts, theta_lb, x, x_int, &msgs); - std::vector > results(ts.size()); + std::vector> results(ts.size()); for (size_t i = 0; i < ode_res_ub.size(); i++) for (size_t j = 0; j < ode_res_ub[j].size(); j++) @@ -44,7 +44,7 @@ std::vector > finite_diff_params( // calculates finite diffs for integrate_ode_rk45 with varying initial positions template -std::vector > finite_diff_initial_position( +std::vector> finite_diff_initial_position( const F& f, const double& t_in, const std::vector& ts, const std::vector& y_in, const std::vector& theta, const std::vector& x, const std::vector& x_int, @@ -62,15 +62,15 @@ std::vector > finite_diff_initial_position( } } - std::vector > ode_res_ub; - std::vector > ode_res_lb; + std::vector> ode_res_ub; + std::vector> ode_res_lb; ode_res_ub = stan::math::integrate_ode_rk45(f, y_in_ub, t_in, ts, theta, x, x_int, &msgs); ode_res_lb = stan::math::integrate_ode_rk45(f, y_in_lb, t_in, ts, theta, x, x_int, &msgs); - std::vector > results(ts.size()); + std::vector> results(ts.size()); for (size_t i = 0; i < ode_res_ub.size(); i++) for (size_t j = 0; j < ode_res_ub[j].size(); j++) @@ -90,7 +90,7 @@ void test_ode_finite_diff_dv(const F& f, const double& t_in, const double& diff2) { std::stringstream msgs; - std::vector > > finite_diff_res(theta.size()); + std::vector>> finite_diff_res(theta.size()); for (size_t i = 0; i < theta.size(); i++) finite_diff_res[i] = finite_diff_params(f, t_in, ts, y_in, theta, x, x_int, i, diff); @@ -101,7 +101,7 @@ void test_ode_finite_diff_dv(const F& f, const double& t_in, for (size_t i = 0; i < theta.size(); i++) theta_v.push_back(theta[i]); - std::vector > ode_res; + std::vector> ode_res; ode_res = stan::math::integrate_ode_rk45(f, y_in, t_in, ts, theta_v, x, x_int, &msgs); @@ -134,7 +134,7 @@ void test_ode_finite_diff_vd(const F& f, const double& t_in, const double& diff2) { std::stringstream msgs; - std::vector > > finite_diff_res(y_in.size()); + std::vector>> finite_diff_res(y_in.size()); for (size_t i = 0; i < y_in.size(); i++) finite_diff_res[i] = finite_diff_initial_position(f, t_in, ts, y_in, theta, x, x_int, i, diff); @@ -145,7 +145,7 @@ void test_ode_finite_diff_vd(const F& f, const double& t_in, for (size_t k = 0; k < y_in.size(); k++) y_in_v.push_back(y_in[k]); - std::vector > ode_res; + std::vector> ode_res; ode_res = stan::math::integrate_ode_rk45(f, y_in_v, t_in, ts, theta, x, x_int, &msgs); @@ -178,14 +178,12 @@ void test_ode_finite_diff_vv(const F& f, const double& t_in, const double& diff2) { std::stringstream msgs; - std::vector > > finite_diff_res_y( - y_in.size()); + std::vector>> finite_diff_res_y(y_in.size()); for (size_t i = 0; i < y_in.size(); i++) finite_diff_res_y[i] = finite_diff_initial_position( f, t_in, ts, y_in, theta, x, x_int, i, diff); - std::vector > > finite_diff_res_p( - theta.size()); + std::vector>> finite_diff_res_p(theta.size()); for (size_t i = 0; i < theta.size(); i++) finite_diff_res_p[i] = finite_diff_params(f, t_in, ts, y_in, theta, x, x_int, i, diff); @@ -204,7 +202,7 @@ void test_ode_finite_diff_vv(const F& f, const double& t_in, for (size_t i = 0; i < theta_v.size(); i++) vars.push_back(theta_v[i]); - std::vector > ode_res; + std::vector> ode_res; ode_res = stan::math::integrate_ode_rk45(f, y_in_v, t_in, ts, theta_v, x, x_int, &msgs); @@ -242,13 +240,13 @@ void test_ode_error_conditions(F& f, const double& t0, using stan::math::integrate_ode_rk45; std::stringstream msgs; - ASSERT_NO_THROW(integrate_ode_rk45(f, y0, t0, ts, theta, x, x_int, 0)); + ASSERT_NO_THROW((integrate_ode_rk45(f, y0, t0, ts, theta, x, x_int, 0))); ASSERT_EQ("", msgs.str()); msgs.clear(); std::vector y0_bad; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs)), std::invalid_argument, "initial state has size 0"); EXPECT_EQ("", msgs.str()); @@ -258,14 +256,14 @@ void test_ode_error_conditions(F& f, const double& t0, expected_msg << "initial time is " << t0_bad << ", but must be less than " << ts[0]; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs)), std::domain_error, expected_msg.str()); EXPECT_EQ("", msgs.str()); msgs.clear(); std::vector ts_bad; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs)), std::invalid_argument, "times has size 0"); EXPECT_EQ("", msgs.str()); @@ -273,14 +271,14 @@ void test_ode_error_conditions(F& f, const double& t0, ts_bad.push_back(3); ts_bad.push_back(1); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs), - std::domain_error, "times is not a valid ordered vector"); + (integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs)), + std::domain_error, "times is not a valid sorted vector"); EXPECT_EQ("", msgs.str()); msgs.clear(); std::vector theta_bad; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs)), std::out_of_range, "vector"); EXPECT_EQ("", msgs.str()); @@ -288,7 +286,7 @@ void test_ode_error_conditions(F& f, const double& t0, msgs.clear(); std::vector x_bad; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs)), std::out_of_range, "vector"); EXPECT_EQ("", msgs.str()); } @@ -297,7 +295,7 @@ void test_ode_error_conditions(F& f, const double& t0, msgs.clear(); std::vector x_int_bad; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta, x, x_int_bad, &msgs), + (integrate_ode_rk45(f, y0, t0, ts, theta, x, x_int_bad, &msgs)), std::out_of_range, "vector"); EXPECT_EQ("", msgs.str()); } @@ -316,27 +314,27 @@ void test_ode_error_conditions_nan(F& f, const double& t0, std::stringstream expected_is_nan; expected_is_nan << "is " << nan; - ASSERT_NO_THROW(integrate_ode_rk45(f, y0, t0, ts, theta, x, x_int, 0)); + ASSERT_NO_THROW((integrate_ode_rk45(f, y0, t0, ts, theta, x, x_int, 0))); ASSERT_EQ("", msgs.str()); msgs.clear(); std::vector y0_bad = y0; y0_bad[0] = nan; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs)), std::domain_error, "initial state"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs)), std::domain_error, expected_is_nan.str()); EXPECT_EQ("", msgs.str()); msgs.clear(); double t0_bad = nan; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs)), std::domain_error, "initial time"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs)), std::domain_error, expected_is_nan.str()); EXPECT_EQ("", msgs.str()); @@ -344,10 +342,10 @@ void test_ode_error_conditions_nan(F& f, const double& t0, std::vector ts_bad = ts; ts_bad[0] = nan; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs)), std::domain_error, "times"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs)), std::domain_error, expected_is_nan.str()); EXPECT_EQ("", msgs.str()); @@ -355,10 +353,10 @@ void test_ode_error_conditions_nan(F& f, const double& t0, std::vector theta_bad = theta; theta_bad[0] = nan; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs), - std::domain_error, "parameter vector"); + (integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs)), + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs)), std::domain_error, expected_is_nan.str()); EXPECT_EQ("", msgs.str()); @@ -367,10 +365,10 @@ void test_ode_error_conditions_nan(F& f, const double& t0, std::vector x_bad = x; x_bad[0] = nan; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs), - std::domain_error, "continuous data"); + (integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs)), + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs)), std::domain_error, expected_is_nan.str()); EXPECT_EQ("", msgs.str()); } @@ -391,41 +389,41 @@ void test_ode_error_conditions_inf(F& f, const double& t0, std::stringstream expected_is_neg_inf; expected_is_neg_inf << "is " << -inf; - ASSERT_NO_THROW(integrate_ode_rk45(f, y0, t0, ts, theta, x, x_int, 0)); + ASSERT_NO_THROW((integrate_ode_rk45(f, y0, t0, ts, theta, x, x_int, 0))); ASSERT_EQ("", msgs.str()); msgs.clear(); std::vector y0_bad = y0; y0_bad[0] = inf; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs)), std::domain_error, "initial state"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs)), std::domain_error, expected_is_inf.str()); y0_bad[0] = -inf; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs)), std::domain_error, "initial state"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0_bad, t0, ts, theta, x, x_int, &msgs)), std::domain_error, expected_is_neg_inf.str()); EXPECT_EQ("", msgs.str()); msgs.clear(); double t0_bad = inf; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs)), std::domain_error, "initial time"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs)), std::domain_error, expected_is_inf.str()); t0_bad = -inf; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs)), std::domain_error, "initial time"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0_bad, ts, theta, x, x_int, &msgs)), std::domain_error, expected_is_neg_inf.str()); EXPECT_EQ("", msgs.str()); @@ -433,17 +431,17 @@ void test_ode_error_conditions_inf(F& f, const double& t0, std::vector ts_bad = ts; ts_bad[0] = inf; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs)), std::domain_error, "times"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs)), std::domain_error, expected_is_inf.str()); ts_bad[0] = -inf; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs)), std::domain_error, "times"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts_bad, theta, x, x_int, &msgs)), std::domain_error, expected_is_neg_inf.str()); EXPECT_EQ("", msgs.str()); @@ -451,17 +449,17 @@ void test_ode_error_conditions_inf(F& f, const double& t0, std::vector theta_bad = theta; theta_bad[0] = inf; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs), - std::domain_error, "parameter vector"); + (integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs)), + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs)), std::domain_error, expected_is_inf.str()); theta_bad[0] = -inf; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs), - std::domain_error, "parameter vector"); + (integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs)), + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts, theta_bad, x, x_int, &msgs)), std::domain_error, expected_is_neg_inf.str()); EXPECT_EQ("", msgs.str()); @@ -470,17 +468,17 @@ void test_ode_error_conditions_inf(F& f, const double& t0, std::vector x_bad = x; x_bad[0] = inf; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs), - std::domain_error, "continuous data"); + (integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs)), + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs)), std::domain_error, expected_is_inf.str()); x_bad[0] = -inf; EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs), - std::domain_error, "continuous data"); + (integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs)), + std::domain_error, "ode parameters and data"); EXPECT_THROW_MSG( - integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs), + (integrate_ode_rk45(f, y0, t0, ts, theta, x_bad, x_int, &msgs)), std::domain_error, expected_is_neg_inf.str()); EXPECT_EQ("", msgs.str()); }