Skip to content

Expression sources and adaptors

This page covers the small expression building blocks in kfr/base.hpp. Most sources are infinite by default. This is convenient for signals and procedural data, but a dynamic container needs a finite extent. Use truncate, render, or trender to establish that bound.

Constant and generated sources

Scalar constants

scalar wraps one explicit scalar expression. It has rank zero, so it broadcasts wherever a larger expression expects a value. zeros and ones are scalar expressions holding zero and one; their element type defaults to fbase and can be specified:

univector<int, 4> zeroes = zeros<int>();
univector<float, 4> gains = ones<float>() * 0.5f;

Counters

counter is an infinite 1D arithmetic source beginning at start with a step of one. The multi-axis form counter accepts one step per axis. Its value is

\[ start + step_0 i_0 + step_1 i_1 + \cdots \]
auto samples = render(counter(10, 2), 4);
// { 10, 12, 14, 16 }

auto grid = trender(truncate(counter(0, 10, 1), shape{ 2, 3 }));
// {{0, 1, 2}, {10, 11, 12}}

Lambda-backed expressions

lambda creates an infinite expression from a callable. Specify its value type and rank as template arguments. The callable may receive an index shape<Dims>, an index plus vectorization parameters, or no arguments. An index-only lambda is usually the clearest choice:

auto checkerboard = lambda<int, 2>([](shape<2> index)
{
    return (index[0] + index[1]) % 2;
});

auto image = trender(checkerboard, shape{ 4, 6 });

lambda_generator has the same calling forms but declares that it is not random-access. Use it for stateful generation where values must be consumed in traversal order. Bound it before materializing it, just as with other infinite generators.

Repeating and evenly spaced sequences

sequence repeats a supplied value list forever:

auto pattern = render(sequence(0.0f, 0.5f, 1.0f), 7);
// { 0, 0.5, 1, 0, 0.5, 1, 0 }

linspace produces evenly spaced values between start and stop. If endpoint is true, stop is the last of size values; otherwise it is excluded. Its final ctrue argument makes the result finite; without it, the source remains infinite after the requested interval.

auto inclusive = render(linspace(0.0, 1.0, 5, true, ctrue));
// { 0, 0.25, 0.5, 0.75, 1 }

auto exclusive = render(linspace(0.0, 1.0, 4, false, ctrue));
// { 0, 0.25, 0.5, 0.75 }

symmlinspace is the symmetric form, spanning [-symsize, +symsize] with endpoints included:

auto positions = render(symmlinspace(3.0, 4, ctrue));
// { -3, -1, 1, 3 }

arange and arange are always finite. They produce [0, stop) or [start, stop) with a given step:

auto indices = render(arange(2, 10, 3));
// { 2, 5, 8 }

Use a nonzero step that moves from start toward stop.

Selecting and extending an expression

slice and truncate

slice creates a rank-preserving view starting at start, with an optional maximum size. Its actual extent is clamped to the source. It forwards reads and, where possible, writes to its operand.

truncate is a slice from the origin. It is the standard way to turn an infinite generator into a finite expression:

auto middle = render(slice(counter(), 100, 4));
// { 100, 101, 102, 103 }

univector<float> finite = truncate(counter<float>(), 256);

For multidimensional sources, pass shapes:

auto block = slice(counter(0, 10, 1), shape{ 2, 3 }, shape{ 2, 4 });

Padding and reversal

padded extends an expression to an infinite source. Requests inside the original shape read from the operand; requests outside it return the fill value.

auto padded_signal = render(padded(truncate(counter(), 3), -1), 6);
// { 0, 1, 2, -1, -1, -1 }

reverse reverses the final axis. Its operand must support random access, and its shape is unchanged:

auto backwards = render(reverse(truncate(counter(), 5)));
// { 4, 3, 2, 1, 0 }

Changing shape and rank

reshape exposes the same logical sequence under another shape. It maps through row-major flat indices, so a 1D sequence {0, 1, 2, 3, 4, 5} reshaped to {2, 3} reads as {{0, 1, 2}, {3, 4, 5}}. Supply a shape with a compatible total element count:

auto matrix = trender(reshape(truncate(counter(), 6), shape{ 2, 3 }));

fixshape is intentionally different. It supplies a compile-time shape declaration but does not remap elements. It is primarily useful when a generic algorithm benefits from a statically known extent:

auto values = truncate(counter<float>(), 16);
auto fixed = fixshape(values, fixed_shape<16>);

dimensions raises an expression to a specified rank by prepending infinite, broadcast axes. It does not duplicate storage:

auto constant_line = truncate(dimensions<1>(scalar(1)), 5);
// { 1, 1, 1, 1, 1 }

Joining and routing expressions

concatenate joins same-rank, same-value-type expressions along axis zero by default. A template axis selects another axis; the three-argument overload joins three inputs. Non-concatenated extents are limited to their common region.

auto joined = render(concatenate(
    truncate(counter(5, 0), 3),
    truncate(counter(10, 0), 2)));
// { 5, 5, 5, 10, 10 }

pack combines aligned scalar expressions into a vector-valued expression. It is useful for assembling interleaved channels. unpack is the output counterpart: it accepts vector values and distributes their lanes to several scalar destinations.

univector<float> left  = truncate(counter(0, 1), 3);
univector<float> right = truncate(counter(100, 1), 3);

// Each value is a two-lane vector: {left[i], right[i]}.
auto stereo = pack(left, right);

// Writing a packed expression writes its lanes back to the channels.
pack(left, right) *= broadcast<2>(10.0f);

Stateful and diagnostic adaptors

adjacent calls fn(current, previous) for each value. The previous value begins as zero. It is stateful and not random-access, so construct a fresh expression for each independent traversal:

auto products = render(adjacent(fn::mul(), counter()), 5);
// { 0, 0, 2, 6, 12 }

trace returns the same values as its input while printing requested vector blocks and their indices. It is a debugging aid rather than a stable presentation format: what is printed depends on traversal and vectorization.

render(trace(counter()), 16); // prints the evaluated blocks to the console