Let's assume I have a system of equations:
x^2 + y^2 = 1
x - y = 0
I want to solve this system by Newton's iterative method: J(x) * delta_x = -f(x)
Where J(x) - the Jacobian of this system.
The question is:What is the best way to calculate the Jacobian and set the system? I have a working version, but it is awkward to set equations and a system in it:
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/lu.hpp>
#include <boost/math/differentiation/autodiff.hpp>
using namespace boost::numeric::ublas;
using namespace boost::math::differentiation;
template<typename X, typename Y>
promote<X, Y> f0(const X &x, const Y &y) {
return x * x + y + y - 1;
}
template<typename X, typename Y>
promote<X, Y> f1(const X &x, const Y &y) {
return x - y;
}
vector<double> f(const vector<double> &var) {
vector<double> f(2, 0);
f[0] = f0(var[0], var[1]);
f[1] = f1(var[0], var[1]);
return f;
}
matrix<double> J(const vector<double> &var) {
matrix<double> J(2, 2, 0);
auto const variables = make_ftuple<double, 1, 1>(var[0], var[1]);
auto const &x = std::get<0>(variables);
auto const &y = std::get<1>(variables);
J(0, 0) = f0(x, y).derivative(1, 0);
J(0, 1) = f0(x, y).derivative(0, 1);
J(1, 0) = f1(x, y).derivative(1, 0);
J(1, 1) = f1(x, y).derivative(0, 1);
return J;
}
int main() {
vector<double> x(2, 0);
x[0] = 0.5;
x[1] = 0.5;
double eps = 1.e-6;
int iter = 0;
vector<double> fx, delta_x;
matrix<double> Jx;
while (iter < 1.e3) {
Jx = J(x);
fx = f(x);
delta_x = -fx;
// Solve the linear system J(x) * delta_x = -f(x)
permutation_matrix<std::size_t> pm(Jx.size1());
lu_factorize(Jx, pm);
lu_substitute(Jx, pm, delta_x);
x += delta_x;
if (norm_2(delta_x) < eps) break;
if (++iter % 10 == 0)
std::cout << "[" << iter << "] " << "Solution: " << x << std::endl;
}
std::cout << "Final answer:" << std::endl;
std::cout << "[" << iter << "] " << "Solution: " << x << std::endl;
return 0;
}
Although you didn't really explain what is "awkward" about the code.
I looked at it for a bit and was able to come up with some optimizations and simplifications (e.g. using C++17 language features).
Note, for clarity (and safety) I use namespace aliases:
namespace U = boost::numeric::ublas; namespace D = boost::math::differentiation;
The optimizations is to avoid unbounded array storage for vectors/matrices because they are fixed size:
template <size_t N, typename T = double> using FixedVector = U::vector<T, std::array<T, N>>;
using Vec = FixedVector<2>;
using Mat = U::matrix<double, U::row_major, std::array<double, 4>>;
I opted for std::array
because it's trivial and constexpr friendly, and (contrary to U::bounded_array<>
) allows initializer-list construction. This will allow us more natural assignments:
Vec f(Vec const& var) {
auto& [x, y] = var.data();
return Vec(2, {f0(x, y), f1(x, y)});
}
Mat J(Vec const& var) {
auto const& [x, y] = D::make_ftuple<double, 1, 1>(var[0], var[1]);
return Mat{2, 2,
{
f0(x, y).derivative(1, 0),
f0(x, y).derivative(0, 1),
f1(x, y).derivative(1, 0),
f1(x, y).derivative(0, 1),
}};
}
The other part is to use structed bindings instead of the get<n>
or [n]
dances.
Looking at this from an efficiency standpoint, I'd verify that the compiler eliminates the common subexpressions in the matrix initializers.
For the f0
/f1
functions I've let type deduction do the work:
constexpr auto f0(auto const& x, auto const& y) { return x * x + y + y - 1; }
constexpr auto f1(auto const& x, auto const& y) { return x - y; }
The main driver hasn't changed much, though I reshaped the loop so it's more conventional:
Live On Coliru
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/operations.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/lu.hpp>
#include <boost/math/differentiation/autodiff.hpp>
constexpr auto f0(auto const& x, auto const& y) { return x * x + y + y - 1; }
constexpr auto f1(auto const& x, auto const& y) { return x - y; }
namespace U = boost::numeric::ublas;
namespace D = boost::math::differentiation;
template <size_t N, typename T = double> using FixedVector = U::vector<T, std::array<T, N>>;
using Vec = FixedVector<2>;
using Mat = U::matrix<double, U::row_major, std::array<double, 4>>;
Vec f(Vec const& var) {
auto& [x, y] = var.data();
return Vec(2, {f0(x, y), f1(x, y)});
}
Mat J(Vec const& var) {
auto const& [x, y] = D::make_ftuple<double, 1, 1>(var[0], var[1]);
return Mat{2, 2,
{
f0(x, y).derivative(1, 0),
f0(x, y).derivative(0, 1),
f1(x, y).derivative(1, 0),
f1(x, y).derivative(0, 1),
}};
}
int main() {
constexpr double eps = 1.e-6;
Vec x(2, {0.5, 0.5});
U::vector<double> fx, delta_x;
U::matrix<double> Jx;
for (auto iteration = 0; iteration < 1'000; ++iteration) {
Jx = J(x);
fx = f(x);
delta_x = -fx;
// Solve the linear system J(x) * delta_x = -f(x)
U::permutation_matrix<std::size_t> pm(Jx.size1());
lu_factorize(Jx, pm);
lu_substitute(Jx, pm, delta_x);
x += delta_x;
std::cout << "[" << iteration << "] Solution: " << x << std::endl;
if (norm_2(delta_x) < eps)
break;
}
std::cout << "Solution: " << x << std::endl;
}
Printing
[0] Solution: [2](0.416667,0.416667)
[1] Solution: [2](0.414216,0.414216)
[2] Solution: [2](0.414214,0.414214)
[3] Solution: [2](0.414214,0.414214)
Solution: [2](0.414214,0.414214)
I expect the changes to make the code marginally faster (it'd be interesting to see a micro-benchmark without the I/O).
I'm not sure whether any of this addressed some of the "awkwardness" that you were referring to in the question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With