Commit fdf94b53 authored by Ben Huber's avatar Ben Huber

fully documented ALS example

parent 5f80d3c9
Pipeline #736 passed with stages
in 8 minutes and 37 seconds
#include <xerus.h>
using namespace xerus;
class InternalSolver {
const size_t d;
std::vector<Tensor> leftAStack;
std::vector<Tensor> rightAStack;
std::vector<Tensor> leftBStack;
std::vector<Tensor> rightBStack;
TTTensor& x;
const TTOperator& A;
const TTTensor& b;
const double solutionsNorm;
public:
size_t maxIterations;
InternalSolver(const TTOperator& _A, TTTensor& _x, const TTTensor& _b)
: d(_x.degree()), x(_x), A(_A), b(_b), solutionsNorm(frob_norm(_b)), maxIterations(1000)
{
leftAStack.emplace_back(Tensor::ones({1,1,1}));
rightAStack.emplace_back(Tensor::ones({1,1,1}));
leftBStack.emplace_back(Tensor::ones({1,1}));
rightBStack.emplace_back(Tensor::ones({1,1}));
}
void push_left_stack(const size_t _position) {
Index i1, i2, i3, j1 , j2, j3, k1, k2;
const Tensor &xi = x.get_component(_position);
const Tensor &Ai = A.get_component(_position);
const Tensor &bi = b.get_component(_position);
Tensor tmpA, tmpB;
tmpA(i1, i2, i3) = leftAStack.back()(j1, j2, j3)
*xi(j1, k1, i1)*Ai(j2, k1, k2, i2)*xi(j3, k2, i3);
leftAStack.emplace_back(std::move(tmpA));
tmpB(i1, i2) = leftBStack.back()(j1, j2)
*xi(j1, k1, i1)*bi(j2, k1, i2);
leftBStack.emplace_back(std::move(tmpB));
}
void push_right_stack(const size_t _position) {
Index i1, i2, i3, j1 , j2, j3, k1, k2;
const Tensor &xi = x.get_component(_position);
const Tensor &Ai = A.get_component(_position);
const Tensor &bi = b.get_component(_position);
Tensor tmpA, tmpB;
tmpA(i1, i2, i3) = xi(i1, k1, j1)*Ai(i2, k1, k2, j2)*xi(i3, k2, j3)
*rightAStack.back()(j1, j2, j3);
rightAStack.emplace_back(std::move(tmpA));
tmpB(i1, i2) = xi(i1, k1, j1)*bi(i2, k1, j2)
*rightBStack.back()(j1, j2);
rightBStack.emplace_back(std::move(tmpB));
}
double calc_residual_norm() {
Index i,j;
return frob_norm(A(i/2, j/2)*x(j&0) - b(i&0)) / solutionsNorm;
}
void solve() {
// Build right stack
x.move_core(0, true);
for (size_t pos = d-1; pos > 0; --pos) {
push_right_stack(pos);
}
Index i1, i2, i3, j1 , j2, j3, k1, k2;
std::vector<double> residuals(10, 1000.0);
for (size_t itr = 0; itr < maxIterations; ++itr) {
// Calculate residual and check end condition
residuals.push_back(calc_residual_norm());
if (residuals.back()/residuals[residuals.size()-10] > 0.99) {
XERUS_LOG(simpleALS, "Done! Residual decrease from " << std::scientific << residuals[10] << " to " << std::scientific << residuals.back() << " in " << residuals.size()-10 << " iterations.");
return; // We are done!
}
XERUS_LOG(simpleALS, "Iteration: " << itr << " Residual: " << residuals.back());
// Sweep Left -> Right
for (size_t corePosition = 0; corePosition < d; ++corePosition) {
Tensor op, rhs;
const Tensor &Ai = A.get_component(corePosition);
const Tensor &bi = b.get_component(corePosition);
op(i1, i2, i3, j1, j2, j3) = leftAStack.back()(i1, k1, j1)*Ai(k1, i2, j2, k2)*rightAStack.back()(i3, k2, j3);
rhs(i1, i2, i3) = leftBStack.back()(i1, k1) * bi(k1, i2, k2) * rightBStack.back()(i3, k2);
xerus::solve(x.component(corePosition), op, rhs);
if (corePosition+1 < d) {
x.move_core(corePosition+1, true);
push_left_stack(corePosition);
rightAStack.pop_back();
rightBStack.pop_back();
}
}
// Sweep Right -> Left : only move core and update stacks
x.move_core(0, true);
for (size_t corePosition = d-1; corePosition > 0; --corePosition) {
push_right_stack(corePosition);
leftAStack.pop_back();
leftBStack.pop_back();
}
}
}
};
void simpleALS(const TTOperator& _A, TTTensor& _x, const TTTensor& _b) {
InternalSolver solver(_A, _x, _b);
return solver.solve();
}
int main() {
Index i,j,k;
auto A = TTOperator::random(std::vector<size_t>(16, 4), std::vector<size_t>(7,2));
A(i/2,j/2) = A(i/2, k/2) * A(j/2, k/2);
auto solution = TTTensor::random(std::vector<size_t>(8, 4), std::vector<size_t>(7,3));
TTTensor b;
b(i&0) = A(i/2, j/2) * solution(j&0);
auto x = TTTensor::random(std::vector<size_t>(8, 4), std::vector<size_t>(7,3));
simpleALS(A, x, b);
XERUS_LOG(info, "Residual: " << frob_norm(A(i/2, j/2) * x(j&0) - b(i&0))/frob_norm(b));
XERUS_LOG(info, "Error: " << frob_norm(solution-x)/frob_norm(x));
}
......@@ -6,68 +6,68 @@ class InternalSolver :
self.b = b
self.x = x
self.d = x.degree()
self.solutionsNorm = b.frob_norm()
self.leftAStack = [ xe.Tensor.ones([1,1,1]) ]
self.leftBStack = [ xe.Tensor.ones([1,1]) ]
self.rightAStack = [ xe.Tensor.ones([1,1,1]) ]
self.rightBStack = [ xe.Tensor.ones([1,1]) ]
self.maxIterations = 1000
def push_left_stack(self, pos) :
i1,i2,i3, j1,j2,j3, k1,k2 = xe.indices(8)
Ai = self.A.get_component(pos)
xi = self.x.get_component(pos)
bi = self.b.get_component(pos)
i1,i2,i3, j1,j2,j3, k1,k2 = xe.indices(8)
tmpA = xe.Tensor()
tmpB = xe.Tensor()
tmpA(i1, i2, i3) << self.leftAStack[-1](j1, j2, j3)*xi(j1, k1, i1)*Ai(j2, k1, k2, i2)*xi(j3, k2, i3)
tmpA(i1, i2, i3) << self.leftAStack[-1](j1, j2, j3)\
*xi(j1, k1, i1)*Ai(j2, k1, k2, i2)*xi(j3, k2, i3)
self.leftAStack.append(tmpA)
tmpB(i1, i2) << self.leftBStack[-1](j1, j2)*xi(j1, k1, i1)*bi(j2, k1, i2)
tmpB(i1, i2) << self.leftBStack[-1](j1, j2)\
*xi(j1, k1, i1)*bi(j2, k1, i2)
self.leftBStack.append(tmpB)
def push_right_stack(self, pos) :
i1,i2,i3, j1,j2,j3, k1,k2 = xe.indices(8)
Ai = self.A.get_component(pos)
xi = self.x.get_component(pos)
bi = self.b.get_component(pos)
i1,i2,i3, j1,j2,j3, k1,k2 = xe.indices(8)
tmpA = xe.Tensor()
tmpB = xe.Tensor()
tmpA(j1, j2, j3) << xi(j1, k1, i1)*Ai(j2, k1, k2, i2)*xi(j3, k2, i3) * self.rightAStack[-1](i1, i2, i3)
tmpA(j1, j2, j3) << xi(j1, k1, i1)*Ai(j2, k1, k2, i2)*xi(j3, k2, i3) \
* self.rightAStack[-1](i1, i2, i3)
self.rightAStack.append(tmpA)
tmpB(j1, j2) << xi(j1, k1, i1)*bi(j2, k1, i2) * self.rightBStack[-1](i1, i2)
tmpB(j1, j2) << xi(j1, k1, i1)*bi(j2, k1, i2) \
* self.rightBStack[-1](i1, i2)
self.rightBStack.append(tmpB)
def calc_residual_norm(self) :
tmp = xe.TTTensor()
i,j = xe.indices(2)
tmp(i&0) << self.A(i/2, j/2)*self.x(j&0)-self.b(i&0)
return xe.frob_norm(tmp)
return xe.frob_norm(self.A(i/2, j/2)*self.x(j&0) - self.b(i&0)) / self.solutionsNorm
def solve(self) :
solutionsNorm = self.b.frob_norm()
residuals = [1000,]*10
maxIterations = 1000
i1,i2,i3, j1,j2,j3, k1,k2 = xe.indices(8)
# build right stack
self.x.move_core(0, True)
for pos in reversed(xrange(1, self.d)) :
self.push_right_stack(pos)
for itr in xrange(maxIterations) :
residuals.append(self.calc_residual_norm()/solutionsNorm)
i1,i2,i3, j1,j2,j3, k1,k2 = xe.indices(8)
residuals = [1000]*10
for itr in xrange(self.maxIterations) :
residuals.append(self.calc_residual_norm())
if residuals[-1]/residuals[-10] > 0.99 :
print("Done! Residual decreased from:", residuals[10], "to", residuals[-1], "in", len(residuals)-10, "sweeps")
return
else:
print("Iteration:",itr, "Residual:", residuals[-1])
print("Iteration:",itr, "Residual:", residuals[-1])
# sweep left -> right
for pos in xrange(self.d):
......@@ -102,11 +102,11 @@ class InternalSolver :
def simpleALS(A, x, b) :
solver = InternalSolver(A, x, b)
solver.solve()
return x
if __name__ == "__main__":
A = xe.TTOperator.random([4]*16, [2]*7)
i,j,k = xe.indices(3)
A = xe.TTOperator.random([4]*16, [2]*7)
A(i/2,j/2) << A(i/2, k/2) * A(j/2, k/2)
solution = xe.TTTensor.random([4]*8, [3]*7)
......@@ -114,8 +114,7 @@ if __name__ == "__main__":
b(i&0) << A(i/2, j/2) * solution(j&0)
x = xe.TTTensor.random([4]*8, [3]*7)
x = simpleALS(A, x, b)
simpleALS(A, x, b)
print("Residual:", xe.frob_norm(A(i/2, j/2) * x(j&0) - b(i&0))/xe.frob_norm(b))
print("Error:", xe.frob_norm(solution-x)/xe.frob_norm(x))
......
This diff is collapsed.
......@@ -31,8 +31,9 @@ this overhead is to write your appliation in c++ instead of python. Most instruc
similar in c++, so a transition might be simpler than you think. Simply check out the rest of the tutorials to compare the code
snippets.
This transition is particularly useful, if you wrote your own numerical algorithms in python. If most of the runtime is spend
inside one of `xerus`'x own algorithms like the `ALS`, it is likely not worth much.
This transition is particularly useful, if you wrote your own numerical algorithms in python. As an example consider the simple
ALS implementation in the example section ([ALS](minimal_als))), where the c++ implementation is faster by about a factor of two.
If most of the runtime is spend inside one of `xerus`'s own algorithms like the `ALS`, it is likely not worth much though.
## Compiling Xerus with High Optimizations
Per default the library already compiles with high optimization settings (corresponding basically to `-O3`) as there is rarely
......
......@@ -54,7 +54,6 @@
#include "xerus/performanceData.h"
#include "xerus/measurments.h"
#include "xerus/algorithms/als.h"
#include "xerus/algorithms/xals.h"
#include "xerus/algorithms/steepestDescent.h"
#include "xerus/algorithms/cg.h"
#include "xerus/algorithms/decompositionAls.h"
......
// Xerus - A General Purpose Tensor Library
// Copyright (C) 2014-2016 Benjamin Huber and Sebastian Wolf.
//
// Xerus is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// Xerus 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Xerus. If not, see <http://www.gnu.org/licenses/>.
//
// For further information on Xerus visit https://libXerus.org
// or contact us at contact@libXerus.org.
/**
* @file
* @brief Header file for the ALS algorithm and its variants.
*/
#pragma once
#include "../ttNetwork.h"
#include "../performanceData.h"
namespace xerus {
/**
* @brief Minimalistic ALS
*/
void xals(TTTensor& _x, const TTOperator& _A, const TTTensor& _b);
}
......@@ -24,6 +24,7 @@
#include "../../include/xerus/misc/internal.h"
using namespace xerus;
static misc::UnitTest als_id("ALS", "identity", [](){
Index k,l,m,n,o,p;
......
// Xerus - A General Purpose Tensor Library
// Copyright (C) 2014-2016 Benjamin Huber and Sebastian Wolf.
//
// Xerus is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// Xerus 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Xerus. If not, see <http://www.gnu.org/licenses/>.
//
// For further information on Xerus visit https://libXerus.org
// or contact us at contact@libXerus.org.
/**
* @file
* @brief Implementation of a simple ALS variant.
*/
#include <xerus/algorithms/xals.h>
#include <xerus/basic.h>
#include <xerus/misc/internal.h>
#include <xerus/indexedTensorMoveable.h>
#include <xerus/misc/basicArraySupport.h>
namespace xerus {
class InternalSolver {
const Index i1, i2, i3, j1 , j2, j3, k1, k2;
const size_t d;
std::vector<Tensor> leftAStack;
std::vector<Tensor> rightAStack;
std::vector<Tensor> leftBStack;
std::vector<Tensor> rightBStack;
TTTensor& x;
const TTOperator& A;
const TTTensor& b;
public:
InternalSolver(TTTensor& _x, const TTOperator& _A, const TTTensor& _b) : d(_x.degree()), x(_x), A(_A), b(_b) {
leftAStack.emplace_back(Tensor::ones({1,1,1}));
rightAStack.emplace_back(Tensor::ones({1,1,1}));
leftBStack.emplace_back(Tensor::ones({1,1}));
rightBStack.emplace_back(Tensor::ones({1,1}));
}
void push_left_stack(const size_t _position) {
const Tensor &xi = x.get_component(_position);
const Tensor &Ai = A.get_component(_position);
const Tensor &bi = b.get_component(_position);
Tensor tmpA, tmpB;
tmpA(i1, i2, i3) = leftAStack.back()(j1, j2, j3)*xi(j1, k1, i1)*Ai(j2, k1, k2, i2)*xi(j3, k2, i3);
leftAStack.emplace_back(std::move(tmpA));
tmpB(i1, i2) = leftBStack.back()(j1, j2)*xi(j1, k1, i1)*bi(j2, k1, i2);
leftBStack.emplace_back(std::move(tmpB));
}
void push_right_stack(const size_t _position) {
const Tensor &xi = x.get_component(_position);
const Tensor &Ai = A.get_component(_position);
const Tensor &bi = b.get_component(_position);
Tensor tmpA, tmpB;
tmpA(i1, i2, i3) = xi(i1, k1, j1)*Ai(i2, k1, k2, j2)*xi(i3, k2, j3)*rightAStack.back()(j1, j2, j3);
rightAStack.emplace_back(std::move(tmpA));
tmpB(i1, i2) = xi(i1, k1, j1)*bi(i2, k1, j2)*rightBStack.back()(j1, j2);
rightBStack.emplace_back(std::move(tmpB));
}
double calc_residual_norm() {
TTTensor tmp;
tmp(i1&0) = A(i1/2, j1/2)*x(j1&0)-b(i1&0);
return frob_norm(tmp);
}
void solve() {
const double solutionsNorm = frob_norm(b);
std::vector<double> residuals(10, 1000.0);
const size_t maxIterations = 1000;
// Rebuild right stack
x.move_core(0, true);
for(size_t pos = d-1; pos > 0; --pos) {
push_right_stack(pos);
}
for(size_t iteration = 0; maxIterations == 0 || iteration < maxIterations; ++iteration) {
// Calculate residual and check end condition
residuals.push_back(calc_residual_norm()/solutionsNorm);
if(residuals.back()/residuals[residuals.size()-10] > 0.99) {
LOG(xALS, "Residual decrease from " << std::scientific << residuals[10] << " to " << std::scientific << residuals.back() << " in " << residuals.size()-10 << " iterations.");
return; // We are done!
} else {
// LOG(xALS, "Residual decrease from " << std::scientific << residuals[10] << " to " << std::scientific << residuals.back() << " in " << residuals.size()-10 << " iterations.");
}
// Sweep Left -> Right
for(size_t corePosition = 0; corePosition < d; ++corePosition) {
Tensor op, rhs;
const Tensor &Ai = A.get_component(corePosition);
const Tensor &bi = b.get_component(corePosition);
op(i1, i2, i3, j1, j2, j3) = leftAStack.back()(i1, k1, j1)*Ai(k1, i2, j2, k2)*rightAStack.back()(i3, k2, j3);
rhs(i1, i2, i3) = leftBStack.back()(i1, k1) * bi(k1, i2, k2) * rightBStack.back()(i3, k2);
xerus::solve(x.component(corePosition), op, rhs, 0);
// If we have not yet reached the end of the sweep we need to take care of the core and update our stacks
if(corePosition+1 < d) {
x.move_core(corePosition+1, true);
push_left_stack(corePosition);
rightAStack.pop_back();
rightBStack.pop_back();
}
}
// Sweep Right -> Left : only move core and update stacks
x.move_core(0, true);
for(size_t corePosition = d-1; corePosition > 0; --corePosition) {
push_right_stack(corePosition);
leftAStack.pop_back();
leftBStack.pop_back();
}
}
}
};
void xals(TTTensor& _x, const TTOperator& _A, const TTTensor& _b) {
InternalSolver solver(_x, _A, _b);
return solver.solve();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment