Configuration
The knobs of TiledLUppConfig, one snippet each. Their roles are
listed on the family page.
Defaults
The default value, and the two solvers named on it. Tiles of 3 cut this 4 x 4 matrix into a full tile and a trailing tile of 1.
// every knob at its default: tiles of 3, right-looking, default thresholds
constexpr tdls::TiledLUppConfig<double> config{};
using Static = tdls::TiledLUppSolverStatic<double, 4, config>;
using Dynamic = tdls::TiledLUppSolverDynamic<double, config>;
// the configuration argument of both solvers defaults to that value
static_assert(std::is_same_v<Static, tdls::TiledLUppSolverStatic<double, 4>>);
static_assert(std::is_same_v<Dynamic, tdls::TiledLUppSolverDynamic<double>>);
int piv[4];
// y holds b on entry and x on exit
const bool ok_static = Static::solve_inplace<true, true, true>(A, 1, piv, 1, y, 1);
const bool ok_dynamic = Dynamic::solve_inplace(4, A2, 1, piv, 1, y2, 1);
Tile size
The same 6 x 6 matrix under two tile sizes: one full tile of 4 with a trailing tile of 2, or a 2 x 2 grid of full tiles of 3.
constexpr auto tiles_of_4 = tdls::TiledLUppConfig<double>{.tile_size = 4};
constexpr auto tiles_of_3 = tdls::TiledLUppConfig<double>{.tile_size = 3};
// 6 x 6: one full tile of 4 and a trailing tile of 2, or a 2 x 2 grid of full tiles
using Tiles4 = tdls::TiledLUppSolverStatic<double, 6, tiles_of_4>;
using Tiles3 = tdls::TiledLUppSolverStatic<double, 6, tiles_of_3>;
static_assert(Tiles4::full_tiles == 1 && Tiles4::last_tile_tail == 2);
static_assert(Tiles3::full_tiles == 2 && Tiles3::last_tile_tail == 0);
int piv[6];
const bool ok4 = Tiles4::solve_inplace<true, true, true>(A4, 1, piv, 1, y4, 1);
const bool ok3 = Tiles3::solve_inplace<true, true, true>(A3, 1, piv, 1, y3, 1);
A single tile
A tile size equal to the dimension: the whole matrix is one tile.
// one tile holds the whole matrix: no trailing update at all
constexpr auto one_tile = tdls::TiledLUppConfig<double>{.tile_size = 4};
using Solver = tdls::TiledLUppSolverStatic<double, 4, one_tile>;
static_assert(Solver::full_tiles == 1 && Solver::last_tile_tail == 0);
int piv[4];
const bool ok = Solver::solve_inplace<true, true, true>(A, 1, piv, 1, y, 1);
A tile larger than the dimension
A tile size above the dimension: the grid is a single partial tile.
// the grid is a single partial tile: 3 of its 8 x 8 slots are used, the others never touched
constexpr auto wide = tdls::TiledLUppConfig<double>{.tile_size = 8};
using Solver = tdls::TiledLUppSolverStatic<double, 3, wide>;
static_assert(Solver::full_tiles == 0 && Solver::last_tile_tail == 3);
int piv[3];
const bool ok = Solver::solve_inplace<true, true, true>(A, 1, piv, 1, y, 1);
Scalar tiles
Tiles of one element: every step handles one pivot.
// tiles of one element: an untiled scalar elimination
constexpr auto scalar = tdls::TiledLUppConfig<double>{.tile_size = 1};
using Solver = tdls::TiledLUppSolverStatic<double, 4, scalar>;
static_assert(Solver::full_tiles == 4 && Solver::last_tile_tail == 0);
int piv[4];
const bool ok = Solver::solve_inplace<true, true, true>(A, 1, piv, 1, y, 1);
Schedules
The two elimination schedules on the same system.
constexpr auto right =
tdls::TiledLUppConfig<double>{.tile_size = 2, .schedule = tdls::Schedule::RightLooking};
constexpr auto left =
tdls::TiledLUppConfig<double>{.tile_size = 2, .schedule = tdls::Schedule::LeftLooking};
using RightLooking = tdls::TiledLUppSolverStatic<double, 4, right>;
using LeftLooking = tdls::TiledLUppSolverStatic<double, 4, left>;
int piv_right[4], piv_left[4];
// the same factors from two update orders
const bool ok_right =
RightLooking::solve_inplace<true, true, true>(A_right, 1, piv_right, 1, y_right, 1);
const bool ok_left =
LeftLooking::solve_inplace<true, true, true>(A_left, 1, piv_left, 1, y_left, 1);
Pivot thresholds
The last pivot is tiny. The default thresholds accept it, raised ones declare the matrix singular.
constexpr auto lenient = tdls::TiledLUppConfig<double>{.tile_size = 2};
constexpr auto strict = tdls::TiledLUppConfig<double>{
.tile_size = 2, .oot_threshold = 1e-6, .singular_floor = 1e-6};
using Lenient = tdls::TiledLUppSolverStatic<double, 4, lenient>;
using Strict = tdls::TiledLUppSolverStatic<double, 4, strict>;
int piv[4];
// the last pivot, 1e-8, reaches the default threshold: accepted inside its tile
const bool ok_lenient = Lenient::solve_inplace<true, true, true>(A, 1, piv, 1, y, 1);
// below 1e-6 the search finds no row left, and the floor declares the matrix singular
const bool ok_strict = Strict::solve_inplace<true, true, true>(A2, 1, piv, 1, y2, 1);
Full out-of-tile scan
Column 0 has no acceptable pivot inside its tile. Two rows below can provide one: the first acceptable, in red, or the largest, in blue.
constexpr auto first = tdls::TiledLUppConfig<double>{.tile_size = 2};
constexpr auto best =
tdls::TiledLUppConfig<double>{.tile_size = 2, .oot_first_acceptable = false};
using First = tdls::TiledLUppSolverStatic<double, 4, first>;
using Best = tdls::TiledLUppSolverStatic<double, 4, best>;
int piv_first[4], piv_best[4];
// both in-tile candidates of column 0 are below 1e-10: the search goes below the tile
const bool ok_first = First::factorize<true, true>(A_first, 1, piv_first, 1);
const bool ok_best = Best::factorize<true, true>(A_best, 1, piv_best, 1);
// first acceptable: row 2, whose 1e-6 reaches the threshold; full scan: row 3, the largest
const bool rows = piv_first[0] == 2 && piv_best[0] == 3;
No forced unrolling
The same solve with and without the unroll pragmas: bitwise identical factors and solution.
constexpr auto unrolled = tdls::TiledLUppConfig<double>{.tile_size = 2};
constexpr auto rolled = tdls::TiledLUppConfig<double>{.tile_size = 2, .unroll_inner = false};
using Unrolled = tdls::TiledLUppSolverStatic<double, 4, unrolled>;
using Rolled = tdls::TiledLUppSolverStatic<double, 4, rolled>;
int piv[4];
// no unroll pragma: same values, faster builds
const bool ok_unrolled =
Unrolled::solve_inplace<true, true, true>(A_unrolled, 1, piv, 1, y_unrolled, 1);
const bool ok_rolled =
Rolled::solve_inplace<true, true, true>(A_rolled, 1, piv, 1, y_rolled, 1);
Column-major layout
The matrix stored column by column, as one system of a batch of 3: the layout knob remaps the flat index, the stride does the rest.
constexpr auto colmajor =
tdls::TiledLUppConfig<double>{.tile_size = 2, .layout = tdls::MatrixLayout::ColMajor};
using Solver = tdls::TiledLUppSolverStatic<double, 4, colmajor>;
// system 1 of an SoA batch of 3: element k of the matrix sits at batch[k * 3 + 1],
// with k = c * 4 + r under the column-major layout
double* A = batch + 1;
const int stride = 3;
int piv[4];
const bool ok = Solver::solve_inplace<true, true, false>(A, stride, piv, 1, y, 1);
Scalar types
The same system in float and in long double.
// float: the acceptable-pivot threshold defaults to 1e-4
using Single = tdls::TiledLUppSolverStatic<float, 4>;
static_assert(Single::oot_threshold == 1e-4f);
// long double: the thresholds are written as double literals and stored exactly
constexpr auto extended =
tdls::TiledLUppConfig<long double>{.oot_threshold = 1e-12, .singular_floor = 1e-300};
using Extended = tdls::TiledLUppSolverStatic<long double, 4, extended>;
static_assert(Extended::oot_threshold == static_cast<long double>(1e-12));
int piv[4];
const bool ok_single =
Single::solve_inplace<true, true, true>(A_single, 1, piv, 1, y_single, 1);
const bool ok_extended =
Extended::solve_inplace<true, true, true>(A_extended, 1, piv, 1, y_extended, 1);
Solver constants
The tile grid and the knobs, read back from the solver type.
constexpr auto config =
tdls::TiledLUppConfig<double>{.tile_size = 4, .schedule = tdls::Schedule::LeftLooking};
using Solver = tdls::TiledLUppSolverStatic<double, 6, config>;
// the tile grid: tile size, full tiles per dimension, tail of the last tile, tile count
static_assert(Solver::tile_size == 4 && Solver::full_tiles == 1 &&
Solver::last_tile_tail == 2 && Solver::num_tiles == 2);
// the knobs, read back from the solver type
static_assert(Solver::schedule == tdls::Schedule::LeftLooking);
static_assert(Solver::oot_threshold == 1e-10);
static_assert(Solver::singular_floor == std::numeric_limits<double>::min());
int piv[6];
const bool ok = Solver::solve_inplace<true, true, true>(A, 1, piv, 1, y, 1);