API Reference

This page documents the public API of HeterogeneousArrays.

Types

HeterogeneousArrays.HeterogeneousVectorType
HeterogeneousVector{T, S} <: AbstractVector{T}

A segmented vector that stores mixed types while maintaining type-stable broadcasting.

Fields can contain scalars (wrapped for mutability), arrays, or quantities with units. The flattened view presents all elements as a single AbstractVector for broadcasting.

Constructors

  • HeterogeneousVector(; kwargs...) - Create with named fields
  • HeterogeneousVector(args...) - Create with positional args (auto-named field1, field2, ...)
  • HeterogeneousVector(x::NamedTuple) - Create from a NamedTuple

Arguments

  • x::NamedTuple: Internal storage (users shouldn't access directly)

Examples

Named fields:

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(x = 1.0, y = 2.5, z = [1, 2, 3]);

julia> v.x
1.0

julia> v.z
3-element Vector{Int64}:
 1
 2
 3
source

Indexing

Base.getindexMethod
Base.getindex(hv::AbstractHeterogeneousVector, idx::Int)

Index into the flattened view of the HeterogeneousVector.

The vector presents a flat 1-based indexed interface where indices are mapped sequentially across all fields in order. Scalar fields count as a single element, and array fields contribute their length to the total.

Arguments

  • hv: The HeterogeneousVector to index
  • idx::Int: The 1-based index into the flattened view

Returns

The element at the given flattened index

Errors

  • Throws BoundsError if idx is outside the range [1, length(hv)]

Performance Warning

This method is not type-stable. The return type depends on which field contains the requested index, and the compiler cannot determine this at compile time. This forces the return type to be a union of all possible field element types, preventing optimization.

For performance-critical code, use named field access instead of integer indexing:

  • v[1] — Not type-stable (avoid in loops)
  • v.field[1] — Type-stable (preferred for performance)

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(a = [1, 2, 3], b = 4.5);

julia> v[1]  # First element of field 'a'
1

julia> v[4]  # Field 'b' (scalar)
4.5

julia> v[5]  # Out of bounds
ERROR: BoundsError
source
Base.setindex!Method
Base.setindex!(hv::AbstractHeterogeneousVector, val, idx::Int)

Assign a value at the flattened index in a HeterogeneousVector.

Index mapping follows the same flattened convention as getindex. For scalar fields, the new value replaces the wrapped value. For array fields, the element is updated in place.

Arguments

  • hv: The HeterogeneousVector to modify
  • val: The new value to assign
  • idx::Int: The 1-based index into the flattened view

Returns

The value that was assigned

Errors

  • Throws BoundsError if idx is outside the range [1, length(hv)]

Performance Warning

Like getindex, this method is not type-stable and should be avoided in performance-critical code. Use named field assignment instead:

  • v[1] = x — Not type-stable (avoid in loops)
  • v.field[1] = x — Type-stable (preferred for performance)

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(a = [1, 2, 3], b = 4.5);

julia> v[2] = 99
99

julia> v.a
3-element Vector{Int64}:
  1
 99
  3

julia> v[4] = 10.0
10.0

julia> v.b
10.0
source
Base.lengthMethod
Base.length(hv::AbstractHeterogeneousVector) -> Int

Return the total length of the HeterogeneousVector as the sum of all field lengths.

Scalar fields (wrapped in Ref) contribute 1 to the total, and array fields contribute their full length. This is the length of the flattened view used for indexing.

Returns

The total number of elements in the flattened representation

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(a = [1, 2, 3], b = 4.5, c = [10, 20]);

julia> length(v)  # 3 (from 'a') + 1 (from 'b') + 2 (from 'c')
6
source
Base.sizeMethod
Base.size(hv::AbstractHeterogeneousVector) -> Tuple

Return the size of the HeterogeneousVector as a 1-tuple of its total length.

This satisfies the AbstractArray interface by returning (length(hv),), representing a 1-dimensional array.

Returns

A tuple (n,) where n = length(hv)

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(x = [1, 2], y = 3.0);

julia> size(v)
(3,)
source
Base.iterateMethod
Base.iterate(hv::AbstractHeterogeneousVector) -> Union{Tuple, Nothing}
Base.iterate(hv::AbstractHeterogeneousVector, state) -> Union{Tuple, Nothing}

Iterate over all elements in the HeterogeneousVector using the flattened view.

The vector is traversed field-by-field in the order they are stored in the internal NamedTuple. Scalar fields yield a single value, and array fields yield each of their elements in sequence.

Returns

  • On first call: (element, state) or nothing if the vector is empty
  • On subsequent calls with state: next (element, state) or nothing when exhausted

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(a = [1, 2], b = 3.0);

julia> for (i, element) in enumerate(v)
           println(i, ": ", element)
       end
1: 1
2: 2
3: 3.0
source

Property Access

Base.getpropertyMethod
Base.getproperty(hv::HeterogeneousVector, name::Symbol)

Access a named field in the HeterogeneousVector.

Arguments

  • hv::HeterogeneousVector: The vector
  • name::Symbol: Field name

Returns

The value of the field (unwrapped if it's a scalar)

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(x = 1.0, y = 2.0);

julia> v.x
1.0

See Also

  • Base.setproperty!(::HeterogeneousVector, ::Symbol, ::Any)
  • propertynames
source
Base.setproperty!Method
Base.setproperty!(hv::HeterogeneousVector, name::Symbol, value)

Set a named field in the HeterogeneousVector.

Arguments

  • hv::HeterogeneousVector: The vector
  • name::Symbol: Field name
  • value: New value for the field

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(x = 1.0, y = 2.0);

julia> v.x = 5.0;

julia> v.x
5.0

See Also

  • Base.getproperty(::HeterogeneousVector, ::Symbol)
  • propertynames
source

Allocation & Copying

Base.copyMethod
Base.copy(bc::Broadcast.Broadcasted{Broadcast.Style{AbstractHeterogeneousVector}}) -> HeterogeneousVector

Materialize a broadcast operation into a new HeterogeneousVector.

When a broadcast expression involves a HeterogeneousVector, this method is called to allocate and fill the result. The operation is performed field-by-field, allowing type-stable operations on each segment independently.

Arguments

  • bc: A broadcasted expression tree rooted at a HeterogeneousVector

Returns

A new HeterogeneousVector containing the broadcasted results, with all fields computed

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(a = [1, 2], b = 3.0);

julia> result = copy(Broadcast.broadcasted(+, v, 2));

julia> result.a
2-element Vector{Int64}:
 3
 4

julia> result.b
5.0
source
Base.copyto!Function
Base.copyto!(dst::AbstractHeterogeneousVector, src::AbstractHeterogeneousVector) -> AbstractHeterogeneousVector

Copy data from a source HeterogeneousVector into a destination HeterogeneousVector.

Both vectors must have the same field names. Data is copied in-place into the destination's existing storage, preserving its array references for array fields and updating scalar wrappings.

Arguments

  • dst: The destination vector (will be modified)
  • src: The source vector (unchanged)

Returns

The modified dst vector

Errors

  • Throws an error if dst and src have different field names

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(a = [1, 2], b = 3.0);

julia> w = zero(v);  # Create an empty vector with same structure

julia> copyto!(w, v);  # Copy data from v into w

julia> w.a
2-element Vector{Int64}:
 1
 2
source
Base.copyto!(dest::AbstractHeterogeneousVector, bc::Broadcast.Broadcasted) -> AbstractHeterogeneousVector

Materialize a broadcast operation in-place into a HeterogeneousVector.

The broadcast result is computed field-by-field and stored directly into the destination vector's existing storage. For array fields, this uses Broadcast.materialize!() for in-place operations. For scalar fields, the result is assigned to the wrapped value.

Arguments

  • dest: The destination HeterogeneousVector (will be modified)
  • bc: A broadcasted expression tree

Returns

The modified dest vector

Errors

  • Throws ArgumentError if the broadcast expression involves a HeterogeneousVector with different field names than dest

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(a = [1.0, 2.0], b = 3.0);

julia> w = HeterogeneousVector(a = [10.0, 20.0], b = 5.0);

julia> copyto!(v, Broadcast.broadcasted(+, v, w));

julia> v.a
2-element Vector{Float64}:
 11.0
 22.0

julia> v.b
8.0
source
Base.copyto!(dest::AbstractArray, bc::Broadcast.Broadcasted{Broadcast.Style{AbstractHeterogeneousVector{Names}}})

Materialize a HeterogeneousVector broadcast result into a flat AbstractArray.

This is the "bridge" between structured heterogeneous data and standard numerical solvers. It allows computing residuals or norms from mixed-unit data and storing them in a plain, contiguous float array.

Storage Layout

The result is flattened field-by-field according to the order in Names. For a vector with fields pos (length 2) and time (length 1):

  • dest[1:2] contains results from pos
  • dest[3] contains results from time

Examples

julia> using HeterogeneousArrays, Unitful

julia> v = HeterogeneousVector(pos = [1.0u"m", 2.0u"m"], time = 10.0u"s");

julia> v_proj = HeterogeneousVector(pos = [1.5u"m", 3.0u"m"], time = 5.0u"s");

julia> # The ODE solver provides a plain Vector{Float64}

julia> residuals = Vector{Float64}(undef, length(v));

julia> # This triggers the flattening copyto!

julia> residuals .= ustrip.(v .- v_proj);

julia> residuals
3-element Vector{Float64}:
 -0.5
 -1.0
  5.0
source
Base.similarMethod
Base.similar(hv::HeterogeneousVector) -> HeterogeneousVector

Create a new HeterogeneousVector with the same structure and field names, with uninitialized storage.

Each field is initialized by calling similar() on its type, creating uninitialized (or zeroed) storage of the same shape and element type as the original. Use zero() if you want zeroed values.

Arguments

  • hv: The template HeterogeneousVector

Returns

A new HeterogeneousVector with the same field names and types, containing uninitialized data

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(a = [1, 2], b = 3.0);

julia> s = similar(v);

julia> length(s.a)
2
source
Base.similar(hv::HeterogeneousVector, ::Type{T}, ::Type{S}, R::DataType...)

Construct a new HeterogeneousVector with the same structure and field names as hv, but with potentially different element types for each individual field.

This variadic method allows for "per-field" type specialization, similar to ArrayPartition in RecursiveArrayTools. It is particularly useful when you need to maintain heterogeneity (e.g., keeping one field as an Int while converting another to a Float64).

Arguments

  • hv: The template HeterogeneousVector.
  • T, S, R...: A sequence of types. The total number of types provided must exactly match the number of fields in hv.

Returns

  • A HeterogeneousVector where the i-th field has the i-th provided type. Note that memory is uninitialized (via similar).

Errors

  • Throws a DimensionMismatch if the number of types provided does not match the number of fields in the vector.

Implementation Note

This function uses ntuple with a compile-time length to ensure the resulting NamedTuple is type-inferred correctly by the Julia compiler.

source
Base.similar(hv::HeterogeneousVector, ::Type{ElType})

Construct a new HeterogeneousVector with the same structure and field names as hv, but with all fields converted to the same uniform element type ElType.

This method satisfies the standard AbstractArray interface. It is essential for ensuring that broadcasting operations (e.g., hv .* 1.0) return a HeterogeneousVector rather than collapsing into a standard flat Array.

Arguments

  • hv: The template HeterogeneousVector.
  • ElType: The target type for all segments/fields within the new vector.

Returns

  • A HeterogeneousVector where every field's elements are of type ElType.

Implementation Note

Uses map over the field names to recursively call _similar_field on each segment, preserving the original NamedTuple keys.

source
similar(hv::HeterogeneousVector; kwargs...)

Construct a new HeterogeneousVector with the same field names and structure as hv, optionally overriding the element types of specific fields.

This method allows for high-level, name-based type transformation. If a field name is provided as a keyword argument, the new vector will use the specified type for that segment. Fields not mentioned in kwargs will preserve their original element types.

Arguments

  • hv::HeterogeneousVector: The template vector providing the names and structure.
  • kwargs...: Pairs of fieldname = Type used to redefine specific segments.

Returns

  • A HeterogeneousVector with uninitialized (or zeroed) data in the requested types.

Errors

  • Throws an ArgumentError if any key in kwargs does not match an existing field name in hv. This prevents silent failures caused by typos in field names.

Performance Note

This implementation avoids Dict allocations by operating directly on the kwargs NamedTuple, making it more efficient and "compiler-friendly" than dictionary-based lookups.

Example

julia> using HeterogeneousArrays, Unitful

julia> v = HeterogeneousVector(pos = [1.0, 2.0]u"m", id = [10, 20]);

julia> # Change 'id' to Float64 and 'pos' to a different unit/type
       v2 = similar(v, id = Float64, pos = Float32);

julia> eltype(v2.id)
Float64

julia> # Typos in field names will now trigger an error
       similar(v, poss = Float64)
ERROR: ArgumentError: Field 'poss' does not exist in HeterogeneousVector. Available fields: (:pos, :id)
source
Base.zeroMethod
Base.zero(hv::HeterogeneousVector) -> HeterogeneousVector

Create a new HeterogeneousVector filled with zero values matching the structure of hv.

Each field is zeroed according to its element type. Scalar fields receive zero(T), and array fields receive zero(array) (an all-zeros array of the same shape).

Arguments

  • hv: The template HeterogeneousVector

Returns

A new HeterogeneousVector with the same field names and types, filled with zeros

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(a = [1, 2], b = 3.0);

julia> z = zero(v);

julia> z.a
2-element Vector{Int64}:
 0
 0

julia> z.b
0.0
source

Broadcasting

Base.Broadcast.BroadcastStyleMethod
Base.BroadcastStyle(::Type{<:AbstractHeterogeneousVector}) -> BroadcastStyle

Define the broadcast style for HeterogeneousVector to enable type-stable broadcasting.

The HeterogeneousVector uses a custom broadcast style to ensure that broadcasting operations preserve the heterogeneous structure and field names. When multiple HeterogeneousVectors are involved in a broadcast operation, they must have compatible field names.

Broadcast Rules

  1. Single HeterogeneousVector with other types: The broadcast result preserves the HeterogeneousVector structure and field names.

  2. Multiple HeterogeneousVectors with matching field names: All vectors must have identical field names; operations proceed field-by-field in parallel.

  3. Multiple HeterogeneousVectors with different field names: Throws an error to prevent silent data corruption.

Examples

julia> using HeterogeneousArrays

julia> v = HeterogeneousVector(a = [1, 2], b = 3.0);

julia> w = HeterogeneousVector(a = [10, 20], b = 5.0);

julia> result = v .+ w;  # Element-wise addition preserves structure

julia> result.a
2-element Vector{Int64}:
 11
 22
source