#pragma once #include #include "drake/common/symbolic/expression.h" #include "drake/math/autodiff.h" #include "drake/math/autodiff_gradient.h" namespace drake { namespace math { namespace internal { template using is_symbolic = std::is_same; template inline constexpr bool is_symbolic_v = is_symbolic::value; template inline constexpr bool is_double_or_symbolic_v = std::disjunction, is_symbolic>::value; template struct is_autodiff : std::false_type {}; template struct is_autodiff> : std::true_type {}; template inline constexpr bool is_autodiff_v = is_autodiff::value; } // namespace internal /** * @anchor linear_solve_given_solver * @name solve linear system of equations with a given solver. * Solve linear system of equations A * x = b. Where A is an Eigen matrix of * double/AutoDiffScalar/symbolic::Expression, and b is an Eigen matrix of * double/AutoDiffScalar/symbolic::Expression. * Notice that if either A or b contains symbolic::Expression, then the other * has to contain symbolic::Expression. * This 3-argument version allows the user to re-use @p linear_solver when * @p b changes or the gradient of @p A changes. When either A or b contains * AutoDiffScalar, we use implicit function * theorem to find the gradient in x as ∂x/∂zᵢ = A⁻¹(∂b/∂zᵢ - ∂A/∂zᵢ * x) where * z is the variable we take gradient with. * * @note When both A and b are Eigen matrix of double, this function is almost * as fast as calling linear_solver.solve(b) directly. When either A or b * contains AutoDiffScalar, this function is a lot faster than first * instantiating the linear solver of AutoDiffScalar, and then solving the * equation with this autodiffable linear solver. * @tparam LinearSolver The type of linear solver, for example * Eigen::LLT * @tparam DerivedA An Eigen Matrix. * @tparam DerivedB An Eigen Vector. * @param linear_solver The linear solver constructed with the double-version of * A. * @param A The matrix A. * @param b The vector b. * * Here is an example code. * @code{cc} * Eigen::Matrix, 2, 2> A_ad; * // Set the value and gradient in A_ad with arbitrary values; * Eigen::Matrix2d A_val; * A_val << 1, 2, 3, 4; * // Gradient of A.col(0). * Eigen::Matrix A0_gradient; * A0_gradient << 1, 2, 3, 4, 5, 6; * A_ad.col(0) = InitializeAutoDiff(A_val.col(0), A0_gradient); * // Gradient of A.col(1) * Eigen::Matrix A1_gradient; * A1_gradient << 7, 8, 9, 10, 11, 12; * A_ad.col(1) = InitializeAutoDiff(A_val.col(1), A1_gradient); * // Set the value and gradient of b to arbitrary value. * const Eigen::Vector2d b_val(2, 3); * Eigen::Matrix b_gradient; * b_gradient << 1, 3, 5, 7, 9, 11; * const auto b_ad = InitializeAutoDiff(b_val, b_gradient); * // Solve the linear system A_val * x_val = b_val. * Eigen::PartialPivLU linear_solver(A_val); * const auto x_val = SolveLinearSystem(linear_solver, A_val, b_val); * // Solve the linear system A*x=b, together with the gradient. * // x_ad contains both the value of the solution A*x=b, together with its * // gradient. * const auto x_ad = SolveLinearSystem(linear_solver, A_ad, b_ad); * @endcode */ //@{ /** * Specialized when A and b are both double or symbolic::Expression matrices. * See @ref linear_solve_given_solver for more details. * Note that @p A is unused, as we already compute its factorization in @p * linear_solver. But we keep it here for consistency with the overloaded * function, where A is a matrix of AutoDiffScalar. */ template typename std::enable_if< internal::is_double_or_symbolic_v && internal::is_double_or_symbolic_v && std::is_same_v, Eigen::Matrix>::type SolveLinearSystem(const LinearSolver& linear_solver, const Eigen::MatrixBase& A, const Eigen::MatrixBase& b) { unused(A); return linear_solver.solve(b); } /** * Specialized when the matrix in linear_solver and b are both double or * symbolic::Expression matrices. * See @ref linear_solve_given_solver for more details. */ template typename std::enable_if< internal::is_double_or_symbolic_v< typename LinearSolver::MatrixType::Scalar> && internal::is_double_or_symbolic_v && std::is_same_v, Eigen::Matrix>::type SolveLinearSystem(const LinearSolver& linear_solver, const Eigen::MatrixBase& b) { return linear_solver.solve(b); } /** * Specialized the matrix in linear_solver is a double-valued matrix and b is * an AutoDiffScalar-valued matrix. See @ref linear_solve_given_solver for more * details. */ template typename std::enable_if< std::is_same_v && internal::is_autodiff_v, Eigen::Matrix>::type SolveLinearSystem(const LinearSolver& linear_solver, const Eigen::MatrixBase& b) { const int num_z_b = GetDerivativeSize(b); if (num_z_b == 0) { return linear_solver.solve(ExtractValue(b)) .template cast(); } const Eigen::Matrix x_val = linear_solver.solve(ExtractValue(b)); Eigen::Matrix grad(b.rows(), num_z_b); Eigen::Matrix x_ad(b.rows(), b.cols()); for (int i = 0; i < b.cols(); ++i) { grad = ExtractGradient(b.col(i)); // ∂x/∂zᵢ = A⁻¹*∂b/∂zᵢ // Before calling linear_solver.solve(grad.eval()), grad contains the // gradient ∂b/∂zᵢ. After calling this function grad contains ∂x/∂zᵢ. I call // grad.eval() to avoid aliasing issue. grad = linear_solver.solve(grad.eval()); x_ad.col(i) = InitializeAutoDiff(x_val.col(i), grad); } return x_ad; } /** * Specialized when A is a double-valued matrix and b is an * AutoDiffScalar-valued matrix. See @ref linear_solve_given_solver for more * details. Note that @p A is unused, as we already compute its factorization in * @p linear_solver. But we keep it here for consistency with the overloaded * function, where A is a matrix of AutoDiffScalar. */ template typename std::enable_if< std::is_same_v && internal::is_autodiff_v, Eigen::Matrix>::type SolveLinearSystem(const LinearSolver& linear_solver, const Eigen::MatrixBase& A, const Eigen::MatrixBase& b) { unused(A); return SolveLinearSystem(linear_solver, b); } /** * Specialized when A is an AutoDiffScalar-valued matrix, and b can contain * either AutoDiffScalar or double. See @ref linear_solve_given_solver for more * details. */ template typename std::enable_if< internal::is_autodiff_v, Eigen::Matrix>::type SolveLinearSystem(const LinearSolver& linear_solver, const Eigen::MatrixBase& A, const Eigen::MatrixBase& b) { // We differentiate A * x = b directly // A*∂x/∂zᵢ + ∂A/∂zᵢ * x = ∂b/∂zᵢ // So ∂x/∂zᵢ = A⁻¹(∂b/∂zᵢ - ∂A/∂zᵢ * x) // where I use z to denote the variable we take derivatives. // The size of the derivatives stored in A and b. const int num_z_A = GetDerivativeSize(A); int num_z_b = 0; if constexpr (!std::is_same_v) { num_z_b = GetDerivativeSize(b); } if (num_z_A == 0 && num_z_b == 0) { if constexpr (std::is_same_v) { return SolveLinearSystem(linear_solver, ExtractValue(A), b) .template cast(); } else { return SolveLinearSystem(linear_solver, ExtractValue(A), ExtractValue(b)) .template cast(); } } else if (num_z_A == 0 && num_z_b != 0) { return SolveLinearSystem(linear_solver, ExtractValue(A), b); } // First compute the value of x. Eigen::Matrix x_val; if constexpr (std::is_same_v) { x_val = linear_solver.solve(b); } else { const auto b_val = ExtractValue(b); x_val = linear_solver.solve(b_val); } // num_z_A != 0 if (num_z_A != 0 && num_z_b != 0 && num_z_A != num_z_b) { throw std::runtime_error( fmt::format("SolveLinearSystem(): A contains derivatives for {} " "variables, while b contains derivatives for {} variables", num_z_A, num_z_b)); } Eigen::Matrix x_ad(A.rows(), b.cols()); // First sets the value of x_ad, and allocates the memory for the derivatives. // Note that I don't call InitializeAutoDiff since I have the // gradient of matrix x w.r.t scalar zᵢ, not the gradient of a vector x.col(j) // w.r.t a vector z. for (int i = 0; i < A.rows(); ++i) { for (int j = 0; j < b.cols(); ++j) { x_ad(i, j).value() = x_val(i, j); x_ad(i, j).derivatives() = Eigen::Matrix::Zero(num_z_A); } } Eigen::Matrix dAdzi(A.rows(), A.cols()); // This stores ∂b/∂zᵢ Eigen::Matrix dbdzi(A.rows(), b.cols()); // This stores ∂x/∂zᵢ Eigen::Matrix dxdzi(A.rows(), b.cols()); for (int i = 0; i < num_z_A; ++i) { dAdzi.setZero(); dbdzi.setZero(); // So ∂x/∂zᵢ = A⁻¹(∂b/∂zᵢ - ∂A/∂zᵢ * x) // First we need to store the matrix ∂A/∂zᵢ for (int j = 0; j < A.rows(); ++j) { for (int k = 0; k < A.cols(); ++k) { if (A(j, k).derivatives().rows() != 0) { dAdzi(j, k) = A(j, k).derivatives()(i); } } } if constexpr (!std::is_same_v) { for (int j = 0; j < b.rows(); ++j) { for (int k = 0; k < b.cols(); ++k) { if (b(j, k).derivatives().rows() != 0) { dbdzi(j, k) = b(j, k).derivatives()(i); } } } } dxdzi = linear_solver.solve(dbdzi - dAdzi * x_val); for (int j = 0; j < A.rows(); ++j) { for (int k = 0; k < b.cols(); ++k) { x_ad(j, k).derivatives()(i) = dxdzi(j, k); } } } return x_ad; } //@} /** * @anchor get_linear_solver * @name Get linear solver * * Create the linear solver for a given matrix A, which will be used to solve * the linear system of equations A * x = b. * * The following table indicate the scalar type of the matrix in the returned * linear solver, depending on the scalar type in matrix A * * | A | double | ADS | Expr | * |------|--------|-------|----- | * |solver| double | double| Expr | * * where ADS stands for Eigen::AutoDiffScalar, and Expr stands for * symbolic::Expression. * Here is the example code * @code{cc} * Eigen::Matrix2d A_val; * A_val << 1, 2, 2, 5; * Eigen::Vector2d b_val(3, 4); * const Eigen::Vector2d x_val = * SolveLinearSystem(GetLinearSolver(A_val), A_val, b_val); * Eigen::Matrix A_ad; * A_ad(0, 0).value() = A_val(0, 0); * A_ad(0, 0).derivatives() = Eigen::Vector3d(1, 2, 3); * A_ad(0, 1).value() = A_val(0, 1); * A_ad(0, 1).derivatives() = Eigen::Vector3d(2, 3, 4); * A_ad(1, 0).value() = A_val(1, 0); * A_ad(1, 0).derivatives() = Eigen::Vector3d(3, 4, 5); * A_ad(1, 1).value() = A_val(1, 1); * A_ad(1, 1).derivatives() = Eigen::Vector3d(4, 5, 6); * // Solve A * x = b with A containing gradient. * const Eigen::Matrix x_ad1 = * SolveLinearSystem(GetLinearSolver(A_ad), A_ad, b_val); * Eigen::Matrix b_ad; * b_ad(0).value() = b_val(0); * b_ad(0).derivatives() = Eigen::Vector3d(5, 6, 7); * b_ad(1).value() = b_val(1); * b_ad(1).derivatives() = Eigen::Vector3d(6, 7, 8); * // Solve A * x = b with b containing gradient. * const Eigen::Matrix x_ad2 = * SolveLinearSystem(GetLinearSolver(A_val), A_val, b_ad); * // Solve A * x = b with both A and b containing gradient. * const Eigen::Matrix x_ad3 = * SolveLinearSystem(GetLinearSolver(A_ad), A_ad, b_ad); * @endcode{cc} */ //@{ namespace internal { /* * The return type of GetLinearSolver function. It is the type of the linear * solver. For example * LinearSolver::type is * Eigen::LLT. * See @ref get_linear_solver for more details. When DerivedA::Scalar is * double or symbolic::Expression, the solver scalar type is the same as * DerivedA::Scalar; when DerivedA::Scalar is Eigen::AutoDiffScalar, the * solver scalar type is double. */ template