Element Collectors

The SimpleCollector classes are growable typed buffers. A collector accumulates elements one by one while the final count is unknown, then hands the result over as a SimpleArray for the typed layer. It is the element-typed counterpart of the byte-level BufferExpander and is built on top of it. Numpy has no counterpart: numpy arrays are fixed-size, so the accumulate-then-convert workflow is where solvcon code reaches for a collector instead of an array.

The exported roster has 13 classes, one per element type: SimpleCollectorBool, SimpleCollectorInt8 through SimpleCollectorInt64, SimpleCollectorUint8 through SimpleCollectorUint64, SimpleCollectorFloat32, SimpleCollectorFloat64, SimpleCollectorComplex64, and SimpleCollectorComplex128.

Note

The growth behavior is like std::vector, but the internal buffer management differs from it. The storage is a shared BufferExpander that as_array() hands over to a SimpleArray, where a std::vector keeps its buffer private and parts with it only by copy or move.

Construction

Two forms are supported:

ct = solvcon.SimpleCollectorFloat64()    # size 0, capacity 0
ct = solvcon.SimpleCollectorFloat64(10)  # size 10, capacity 10

The sized form allocates storage for the given element count and sets both the size and the capacity to it; the elements are not initialized. The sized form also accepts an optional alignment as the second argument (see below).

Sizing and Growing

A collector distinguishes its size (the elements currently stored) from its capacity (the elements allocated), both counted in elements. len(ct) returns the size and the capacity property returns the capacity. reserve(cap) grows the capacity while keeping the size, and expand(length) reserves and then sets the size, mirroring the methods of BufferExpander at element granularity.

push_back(value) appends one element, growing the capacity when full: an empty collector allocates capacity 1, and a full collector doubles its capacity. The amortized-doubling growth keeps a long accumulation loop cheap:

ct = solvcon.SimpleCollectorFloat64()
for it in range(6):
    ct.push_back(it * 1.1)
assert len(ct) == 6
assert ct.capacity == 8   # grew 0, 1, 2, 4, 8

clear() resets the size to zero and keeps the capacity and alignment, so the collector can be refilled without reallocating.

Element Access

Indexing reads and writes single elements of the collector’s value type, so a collector sized up front can also be filled by index instead of by appending:

ct = solvcon.SimpleCollectorFloat64()
ct.expand(6)
for it in range(6):
    ct[it] = float(it)

Bounds are checked against the size, not the capacity, so a reserved-but-unfilled slot is not addressable:

ct = solvcon.SimpleCollectorFloat64()
ct.reserve(6)
ct[0]  # IndexError: SimpleCollector: index 0 is out of bounds ...

Converting to a SimpleArray

as_array() returns a one-dimensional SimpleArray of the matching dtype whose length is the collector’s size:

ct = solvcon.SimpleCollectorFloat64()
for it in range(6):
    ct.push_back(float(it))
arr = ct.as_array()
assert list(arr) == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]

The conversion converts the underlying expander in place: the expander adopts a ConcreteBuffer as its storage, and the returned array wraps that buffer, so from then on the array and the collector share memory and a write through one side is visible through the other. The alignment of the collector carries over to the array. The conversion also sets the capacity equal to the size, so a subsequent push_back reallocates the storage and detaches the two objects; the previously returned array keeps the old memory, as does clear(), which only resets the size and leaves the shared content in place.

Alignment

Like the arrays, a collector accepts a solvcon-specific alignment with no numpy analogue, passed as the second constructor argument:

ct = solvcon.SimpleCollectorFloat64(16, 16)
assert ct.alignment == 16

Valid values are 0 (the default), 16, 32, and 64 bytes; any other value raises ValueError. With a non-zero alignment, the byte count of every allocation (the element count times the item size) must be a multiple of the alignment, or the operation raises ValueError; the check applies to the sized constructor, to reserve, to the allocation behind as_array, and to the growth done by push_back:

ct = solvcon.SimpleCollectorFloat64(16, 16)  # 128 bytes, multiple of 16
ct.reserve(33)
# ValueError: BufferExpander::allocate: size ... must be a multiple ...

An aligned collector must therefore start from a size that satisfies the multiple constraint: an empty 16-byte-aligned SimpleCollectorFloat64 cannot push_back, because the initial capacity-one growth would allocate 8 bytes against the 16-byte alignment.

The read-only alignment property returns the requested value, and the array produced by as_array() reports the same alignment.