/* Copyright 2013 SINTEF ICT, Applied Mathematics. This file is part of the Open Porous Media project (OPM). OPM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OPM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OPM. If not, see . */ #ifndef OPM_AUTODIFFBLOCK_HEADER_INCLUDED #define OPM_AUTODIFFBLOCK_HEADER_INCLUDED #include #include #include #include namespace Opm { /// A class for forward-mode automatic differentiation with vector /// values and sparse jacobian matrices. /// /// The class contains a (column) vector of values and multiple /// sparse matrices representing its partial derivatives. Each /// such matrix has a number of rows equal to the number of rows /// in the value vector, and a number of columns equal to the /// number of discrete variables we want to compute the /// derivatives with respect to. The reason to have multiple such /// jacobians instead of just one is to allow simpler grouping of /// variables, making it easier to implement various /// preconditioning schemes. Only basic arithmetic operators are /// implemented for this class, reflecting our needs so far. /// /// The class is built on the Eigen library, using an Eigen array /// type to contain the values and Eigen sparse matrices for the /// jacobians. The overloaded operators are intended to behave in /// a similar way to Eigen arrays, meaning for example that the * /// operator is elementwise multiplication. The only exception is /// multiplication with a sparse matrix from the left, which is /// treated as an Eigen matrix operation. /// /// There are no public constructors, instead we use the Named /// Constructor pattern. In general, one needs to know which /// variables one wants to compute the derivatives with respect to /// before constructing an AutoDiffBlock. Some of the constructors /// require you to pass a block pattern. This should be a vector /// containing the number of columns you want for each jacobian /// matrix. /// /// For example: you want the derivatives with respect to three /// different variables p, r and s. Assuming that there are 10 /// elements in p, and 20 in each of r and s, the block pattern is /// { 10, 20, 20 }. When creating the variables p, r and s in your /// program you have two options: /// - Use the variable() constructor three times, passing the /// index (0 for p, 1 for r and 2 for s), initial value of /// each variable and the block pattern. /// - Use the variables() constructor passing only the initial /// values of each variable. The block pattern will be /// inferred from the size of the initial value vectors. /// This is usually the simplest option if you have multiple /// variables. Note that this constructor returns a vector /// of all three variables, so you need to use index access /// (operator[]) to get the individual variables (that is p, /// r and s). /// /// After this, the r variable for example will have a size() of /// 20 and three jacobian matrices. The first is a 20 by 10 zero /// matrix, the second is a 20 by 20 identity matrix, and the /// third is a 20 by 20 zero matrix. template class AutoDiffBlock { public: /// Underlying type for values. typedef Eigen::Array V; /// Underlying type for jacobians. typedef Eigen::SparseMatrix M; /// Construct an empty AutoDiffBlock. static AutoDiffBlock null() { V val; std::vector jac; return AutoDiffBlock(val, jac); } /// Create an AutoDiffBlock representing a constant. /// \param[in] val values static AutoDiffBlock constant(const V& val) { return AutoDiffBlock(val); } /// Create an AutoDiffBlock representing a constant. /// This variant requires specifying the block sizes used /// for the Jacobians even though the Jacobian matrices /// themselves will be zero. /// \param[in] val values /// \param[in] blocksizes block pattern static AutoDiffBlock constant(const V& val, const std::vector& blocksizes) { std::vector jac; const int num_elem = val.size(); const int num_blocks = blocksizes.size(); // For constants, all jacobian blocks are zero. jac.resize(num_blocks); for (int i = 0; i < num_blocks; ++i) { jac[i] = M(num_elem, blocksizes[i]); } return AutoDiffBlock(val, jac); } /// Create an AutoDiffBlock representing a single variable block. /// \param[in] index index of the variable you are constructing /// \param[in] val values /// \param[in] blocksizes block pattern /// The resulting object will have size() equal to block_pattern[index]. /// Its jacobians will all be zero, except for derivative()[index], which /// will be an identity matrix. static AutoDiffBlock variable(const int index, const V& val, const std::vector& blocksizes) { std::vector jac; const int num_elem = val.size(); const int num_blocks = blocksizes.size(); // First, set all jacobian blocks to zero... jac.resize(num_blocks); for (int i = 0; i < num_blocks; ++i) { jac[i] = M(num_elem, blocksizes[i]); } // ... then set the one corrresponding to this variable to identity. assert(blocksizes[index] == num_elem); jac[index].reserve(Eigen::VectorXi::Constant(val.size(), 1)); for (typename M::Index row = 0; row < val.size(); ++row) { jac[index].insert(row, row) = Scalar(1.0); } return AutoDiffBlock(val, jac); } /// Create an AutoDiffBlock by directly specifying values and jacobians. /// \param[in] val values /// \param[in] jac vector of jacobians static AutoDiffBlock function(const V& val, const std::vector& jac) { return AutoDiffBlock(val, jac); } /// Construct a set of primary variables, each initialized to /// a given vector. static std::vector variables(const std::vector& initial_values) { const int num_vars = initial_values.size(); std::vector bpat; for (int v = 0; v < num_vars; ++v) { bpat.push_back(initial_values[v].size()); } std::vector vars; for (int v = 0; v < num_vars; ++v) { vars.emplace_back(variable(v, initial_values[v], bpat)); } return vars; } /// Elementwise operator += AutoDiffBlock& operator+=(const AutoDiffBlock& rhs) { if (jac_.empty()) { jac_ = rhs.jac_; } else if (!rhs.jac_.empty()) { assert (numBlocks() == rhs.numBlocks()); assert (value().size() == rhs.value().size()); const int num_blocks = numBlocks(); for (int block = 0; block < num_blocks; ++block) { assert(jac_[block].rows() == rhs.jac_[block].rows()); assert(jac_[block].cols() == rhs.jac_[block].cols()); jac_[block] += rhs.jac_[block]; } } val_ += rhs.val_; return *this; } /// Elementwise operator + AutoDiffBlock operator+(const AutoDiffBlock& rhs) const { if (jac_.empty() && rhs.jac_.empty()) { return constant(val_ + rhs.val_); } if (jac_.empty()) { return val_ + rhs; } if (rhs.jac_.empty()) { return *this + rhs.val_; } std::vector jac = jac_; assert(numBlocks() == rhs.numBlocks()); int num_blocks = numBlocks(); for (int block = 0; block < num_blocks; ++block) { assert(jac[block].rows() == rhs.jac_[block].rows()); assert(jac[block].cols() == rhs.jac_[block].cols()); jac[block] += rhs.jac_[block]; } return function(val_ + rhs.val_, jac); } /// Elementwise operator - AutoDiffBlock operator-(const AutoDiffBlock& rhs) const { if (jac_.empty() && rhs.jac_.empty()) { return constant(val_ - rhs.val_); } if (jac_.empty()) { return val_ - rhs; } if (rhs.jac_.empty()) { return *this - rhs.val_; } std::vector jac = jac_; assert(numBlocks() == rhs.numBlocks()); int num_blocks = numBlocks(); for (int block = 0; block < num_blocks; ++block) { assert(jac[block].rows() == rhs.jac_[block].rows()); assert(jac[block].cols() == rhs.jac_[block].cols()); jac[block] -= rhs.jac_[block]; } return function(val_ - rhs.val_, jac); } /// Elementwise operator * AutoDiffBlock operator*(const AutoDiffBlock& rhs) const { if (jac_.empty() && rhs.jac_.empty()) { return constant(val_ * rhs.val_); } if (jac_.empty()) { return val_ * rhs; } if (rhs.jac_.empty()) { return *this * rhs.val_; } int num_blocks = numBlocks(); std::vector jac(num_blocks); assert(numBlocks() == rhs.numBlocks()); typedef Eigen::DiagonalMatrix D; D D1 = val_.matrix().asDiagonal(); D D2 = rhs.val_.matrix().asDiagonal(); for (int block = 0; block < num_blocks; ++block) { assert(jac_[block].rows() == rhs.jac_[block].rows()); assert(jac_[block].cols() == rhs.jac_[block].cols()); jac[block] = D2*jac_[block] + D1*rhs.jac_[block]; } return function(val_ * rhs.val_, jac); } /// Elementwise operator / AutoDiffBlock operator/(const AutoDiffBlock& rhs) const { if (jac_.empty() && rhs.jac_.empty()) { return constant(val_ / rhs.val_); } if (jac_.empty()) { return val_ / rhs; } if (rhs.jac_.empty()) { return *this / rhs.val_; } int num_blocks = numBlocks(); std::vector jac(num_blocks); assert(numBlocks() == rhs.numBlocks()); typedef Eigen::DiagonalMatrix D; D D1 = val_.matrix().asDiagonal(); D D2 = rhs.val_.matrix().asDiagonal(); D D3 = (1.0/(rhs.val_*rhs.val_)).matrix().asDiagonal(); for (int block = 0; block < num_blocks; ++block) { assert(jac_[block].rows() == rhs.jac_[block].rows()); assert(jac_[block].cols() == rhs.jac_[block].cols()); jac[block] = D3 * (D2*jac_[block] - D1*rhs.jac_[block]); } return function(val_ / rhs.val_, jac); } /// I/O. template Ostream& print(Ostream& os) const { int num_blocks = jac_.size(); os << "Value =\n" << val_ << "\n\nJacobian =\n"; for (int i = 0; i < num_blocks; ++i) { os << "Sub Jacobian #" << i << '\n' << jac_[i] << "\n"; } return os; } /// Number of elements int size() const { return val_.size(); } /// Number of Jacobian blocks. int numBlocks() const { return jac_.size(); } /// Sizes (number of columns) of Jacobian blocks. std::vector blockPattern() const { const int nb = numBlocks(); std::vector bp(nb); for (int block = 0; block < nb; ++block) { bp[block] = jac_[block].cols(); } return bp; } /// Function value. const V& value() const { return val_; } /// Function derivatives. const std::vector& derivative() const { return jac_; } private: AutoDiffBlock(const V& val) : val_(val) { } AutoDiffBlock(const V& val, const std::vector& jac) : val_(val), jac_(jac) { #ifndef NDEBUG const int num_elem = val_.size(); const int num_blocks = jac_.size(); for (int block = 0; block < num_blocks; ++block) { assert(num_elem == jac_[block].rows()); } #endif } V val_; std::vector jac_; }; // --------- Free functions and operators for AutoDiffBlock --------- /// Stream output. template Ostream& operator<<(Ostream& os, const AutoDiffBlock& fw) { return fw.print(os); } /// Multiply with sparse matrix from the left. template AutoDiffBlock operator*(const typename AutoDiffBlock::M& lhs, const AutoDiffBlock& rhs) { int num_blocks = rhs.numBlocks(); std::vector::M> jac(num_blocks); assert(lhs.cols() == rhs.value().rows()); for (int block = 0; block < num_blocks; ++block) { jac[block] = lhs*rhs.derivative()[block]; } typename AutoDiffBlock::V val = lhs*rhs.value().matrix(); return AutoDiffBlock::function(val, jac); } /// Elementwise multiplication with constant on the left. template AutoDiffBlock operator*(const typename AutoDiffBlock::V& lhs, const AutoDiffBlock& rhs) { return AutoDiffBlock::constant(lhs, rhs.blockPattern()) * rhs; } /// Elementwise multiplication with constant on the right. template AutoDiffBlock operator*(const AutoDiffBlock& lhs, const typename AutoDiffBlock::V& rhs) { return rhs * lhs; // Commutative operation. } /// Elementwise addition with constant on the left. template AutoDiffBlock operator+(const typename AutoDiffBlock::V& lhs, const AutoDiffBlock& rhs) { return AutoDiffBlock::constant(lhs, rhs.blockPattern()) + rhs; } /// Elementwise addition with constant on the right. template AutoDiffBlock operator+(const AutoDiffBlock& lhs, const typename AutoDiffBlock::V& rhs) { return rhs + lhs; // Commutative operation. } /// Elementwise subtraction with constant on the left. template AutoDiffBlock operator-(const typename AutoDiffBlock::V& lhs, const AutoDiffBlock& rhs) { return AutoDiffBlock::constant(lhs, rhs.blockPattern()) - rhs; } /// Elementwise subtraction with constant on the right. template AutoDiffBlock operator-(const AutoDiffBlock& lhs, const typename AutoDiffBlock::V& rhs) { return lhs - AutoDiffBlock::constant(rhs, lhs.blockPattern()); } /// Elementwise division with constant on the left. template AutoDiffBlock operator/(const typename AutoDiffBlock::V& lhs, const AutoDiffBlock& rhs) { return AutoDiffBlock::constant(lhs, rhs.blockPattern()) / rhs; } /// Elementwise division with constant on the right. template AutoDiffBlock operator/(const AutoDiffBlock& lhs, const typename AutoDiffBlock::V& rhs) { return lhs / AutoDiffBlock::constant(rhs, lhs.blockPattern()); } /** * @brief Operator for multiplication with a scalar on the right-hand side * * @param lhs The left-hand side AD forward block * @param rhs The scalar to multiply with * @return The product */ template AutoDiffBlock operator*(const AutoDiffBlock& lhs, const Scalar& rhs) { std::vector< typename AutoDiffBlock::M > jac; jac.reserve( lhs.numBlocks() ); for (int block=0; block::function( lhs.value() * rhs, jac ); } /** * @brief Operator for multiplication with a scalar on the left-hand side * * @param lhs The scalar to multiply with * @param rhs The right-hand side AD forward block * @return The product */ template AutoDiffBlock operator*(const Scalar& lhs, const AutoDiffBlock& rhs) { return rhs * lhs; // Commutative operation. } } // namespace Opm #endif // OPM_AUTODIFFBLOCK_HEADER_INCLUDED