Vector instance VECINST command

Commands in this namespace document operations accepted by an individual Rbc vector command.

VECINST is used below as a placeholder for the actual vector command:

::rbc::vector create x
x set {1 2 3 4 5}
puts [x length]

Vector indices

Vector indices are Tcl-sized integer values adjusted by the vector’s current offset.

The index end refers to the last value. Tcl index expressions based on end are supported, for example:

x index end
x index end-1

The special index ++end denotes the position immediately after the final value and may be used when setting a value to append one element:

x index ++end 10.0

Colon-separated ranges may be used by operations that accept ranges:

x index 2:5

An omitted range endpoint denotes the beginning or end of the vector:

x index 2:
x index :5
x index :

For real vectors, the associated Tcl array additionally supports the special calculated indices min, max, mean, sum, and prod. These calculated indices are not available for complex vectors.

Complex vector operations

Most vector instance operations support complex vectors. The following table summarizes the current status.

Operation

Complex vectors

*

Supported

+

Supported

-

Supported

/

Supported

append

Supported

binread

Supported

clear

Supported

delete

Supported

dup

Supported

expr

Supported

fft

Supported

fftfreq

Not supported

ifft

Supported

index

Supported

length

Supported

merge

Supported

normalize

Not supported

offset

Supported

populate

Supported

random

Supported

readonly

Supported

range

Supported

search

Supported

seq

Supported

set

Supported

sort

Not supported

split

Supported

type

Supported

variable

Supported

window

Not supported

Operations that combine vector storage, such as append, merge, set, dup, and split, preserve vector types rather than reinterpreting real storage as complex storage or vice versa.

normalize remains real-only because its definition depends on a minimum and maximum ordering. sort likewise remains real-only because complex numbers have no natural ordering relation.

search supports exact-value searches on complex vectors. A two-bound range search is not defined for complex values and is rejected.

The calculated Tcl array indices min, max, mean, sum, and prod remain real-only. Ordinary numeric indices and ranges work with an explicitly mapped complex vector.

Vector expressions

Vector expressions perform arithmetic, comparison, logical, and mathematical operations on vectors.

Expressions can be evaluated with the top-level vector expr command:

vector expr {expression}

or with the expr operation of a vector instance:

VECINST expr {expression}

The top-level form returns the result. The vector-instance form stores the result in VECINST.

For example:

vector create x y

x set {1 2 3 4}
y set {10 20 30 40}

vector expr {x + y}

# -> 11.0 22.0 33.0 44.0
x expr {x * 2}
x range 0 end
# -> 2.0 4.0 6.0 8.0

Scalars and vectors

A numeric scalar is treated as a single value. When one operand is a scalar and the other is a vector, the scalar operation is applied to every component of the vector.

For example:

vector expr {x * 2}
vector expr {x + 10}
vector expr {100 - x}

When both operands are vectors, the operation is performed component by component. The vectors must have compatible lengths.

For example, given:

x set {1 2 3}
y set {10 20 30}

the expression:

vector expr {x * y}

returns:

10.0 40.0 90.0

Vector names, numeric values, Tcl variable substitutions, command substitutions, quoted or braced values, and parenthesized subexpressions may be used as expression operands.

For example:

set scale 10

vector expr {x * $scale}
vector expr {x + [y index 0]}
vector expr {(x + y) / 2}

Operators

The following operators are supported, listed from highest to lowest precedence.

Operators

Description

- !

Unary minus and logical NOT

^

Exponentiation

* / %

Multiplication, division, and remainder

+ -

Addition and subtraction

<< >>

Circular left and right vector shift

< > <= >=

Relational comparison

== !=

Equality and inequality comparison

&&

Logical AND

\|\|

Logical OR

Binary operators at the same precedence level are evaluated from left to right.

Arithmetic operators operate component by component.

For example:

x set {1 2 3}

vector expr {x + 10}
# -> 11.0 12.0 13.0

vector expr {x * 2}
# -> 2.0 4.0 6.0

Comparison operators return 1.0 when the comparison is true and 0.0 when it is false.

For example:

x set {1 5 10}

vector expr {x > 4}
# -> 0.0 1.0 1.0

Logical operators similarly produce 1.0 or 0.0.

The unary ! operator returns 1.0 for a zero-valued component and 0.0 for a non-zero component.

Circular shifts

The << and >> operators perform circular shifts of vector components. They are not integer bit-shift operators.

The right-hand operand must be a scalar specifying the number of positions to shift.

For example:

x set {1 2 3 4}

vector expr {x << 1}
# -> 2.0 3.0 4.0 1.0

vector expr {x >> 1}
# -> 4.0 1.0 2.0 3.0

Values shifted past one end of the vector reappear at the opposite end.

Complex expression operators

Real and complex vectors may be mixed in an expression. Real operands are promoted to complex values with zero imaginary component when required.

Complex expressions support:

  • unary -

  • unary !

  • +, -, *, and /

  • exponentiation with ^

  • circular shifts << and >>

  • equality and inequality with == and !=

  • logical && and ||.

The remainder operator % and the ordered comparisons <, >, <=, and >= are not supported when either operand is complex.

Complex exponentiation uses the principal complex value for non-integral exponents. A finite integral real exponent is handled directly and therefore does not depend on the logarithm branch.

Equality compares both the real and imaginary components. Logical operations treat a complex value as false only when both components are zero. Equality and logical operations return real 0.0 or 1.0 values.

Circular shifts preserve complete complex values. The shift count must be real; a complex scalar with zero imaginary component is accepted as a real shift count.

Mathematical functions

Vector expressions provide mathematical functions in three general categories:

  • component functions, which operate independently on each vector component

  • scalar functions, which reduce a vector to one value

  • vector functions, which transform a vector into another vector.

Component functions

Component functions apply the selected operation independently to every vector component and return a vector of the same length.

Function

Description

abs

Absolute value

acos

Arc cosine

arg

Phase angle of the component

asin

Arc sine

atan

Arc tangent

ceil

Smallest integral value not less than the component

conj

Complex conjugate

cos

Cosine

cosh

Hyperbolic cosine

exp

Exponential

floor

Largest integral value not greater than the component

imag

Imaginary component

log

Natural logarithm

log10

Base-10 logarithm

random

Generates a pseudo-random value in the range [0.0, 1.0) for each component

real

Real component

round

Rounds to the nearest integral value

sin

Sine

sinh

Hyperbolic sine

sqrt

Square root

tan

Tangent

tanh

Hyperbolic tangent

For example:

x set {1 4 9 16}

vector expr {sqrt(x)}
# -> 1.0 2.0 3.0 4.0

Functions may be combined with operators and other functions:

vector expr {sin(x) * 2}
vector expr {sqrt(abs(x))}

The random function ignores the values of its input components and generates one pseudo-random value for each component:

x length 100

set values [vector expr {random(x)}]

The number of generated values is therefore determined by the length of the argument vector.

Scalar functions

Scalar functions reduce a vector to a single numeric value.

Function

Description

adev

Average absolute deviation from the mean

kurtosis

Fisher excess kurtosis

length

Number of finite components

max

Maximum finite component

mean

Arithmetic mean

median

Median value

min

Minimum finite component

nz

Number of non-zero components

prod

Product of the components

q1

First quartile

q3

Third quartile

sdev

Sample standard deviation

skew

Skewness

sum

Sum of the components

var

Sample variance

For example:

x set {1 2 3 4 5}

vector expr {mean(x)}
# -> 3.0

vector expr {sum(x)}
# -> 15.0

vector expr {min(x)}
# -> 1.0

vector expr {max(x)}
# -> 5.0

Scalar functions can participate in larger expressions:

vector expr {x - mean(x)}
vector expr {x / max(x)}

Vector functions

Vector functions transform their argument and return another vector.

Function

Description

norm

Normalizes the components to the range [0.0, 1.0]

sort

Returns the components sorted in ascending order

For example:

x set {30 10 20}

vector expr {sort(x)}
# -> 10.0 20.0 30.0

Normalization maps the minimum component to 0.0 and the maximum component to 1.0:

x set {10 20 30}

vector expr {norm(x)}
# -> 0.0 0.5 1.0

Mathematical functions on complex vectors

Mathematical functions that have an unambiguous complex definition are supported directly.

The projection functions return real values:

  • abs(z) returns the magnitude |z|.

  • arg(z) returns the phase angle.

  • real(z) returns the real component.

  • imag(z) returns the imaginary component.

conj, sqrt, exp, log, log10, sin, cos, tan, sinh, cosh, tanh, asin, acos, and atan return complex values when their input is complex.

ceil, floor, and round operate independently on the real and imaginary components.

random applied to a complex vector produces a complex vector in which the real and imaginary components are generated independently in the range [0.0, 1.0).

The following reductions support complex vectors:

Function

Result

Complex definition

adev

Real

Mean of abs(z - mean(z))

length

Real

Number of finite complex values

mean

Complex

Arithmetic mean

nz

Real

Number of values other than 0+0i

prod

Complex

Product of the values

sdev

Real

Square root of the Hermitian sample variance

sum

Complex

Sum of the values

var

Real

Hermitian sample variance Σ abs(z - mean(z))^2 / (n - 1)

kurtosis, max, median, min, q1, q3, and skew remain real-only. The vector functions norm and sort are also real-only.

No implicit ordering of complex numbers is introduced for these operations.

Tcl callbacks with map

map(commandPrefix, expression) applies a Tcl command to each element of a vector expression and returns a temporary vector of the same length. Its result can be used with the other expression functions and operators, or assigned with VECINST expr. It does not create a named vector.

The command prefix is a nonempty Tcl list containing a command name and any fixed arguments. RBC appends exactly one argument for the current element and invokes the command in the expression caller’s Tcl frame. A real input element is passed as a scalar. A complex input element is passed as one {real imaginary} list; the callback is responsible for unpacking it.

package require rbc::vector
rbc::vector create x y
x set {1 2 3}

proc adjust {gain offset value} {
    expr {$gain * $value + $offset}
}
y expr {map({adjust 2.0 0.5}, x) + 1}
# y contains 3.5 5.5 7.5

The first argument can be a bare command name, a braced or quoted prefix, a Tcl variable, or a bracketed command substitution. Prefix substitutions are evaluated once per map call. Use braces to protect fixed arguments containing commas, spaces, or parentheses. The prefix is a list, not a script; its elements are passed directly as command arguments without concatenation or another round of substitution.

set callback [list adjust 2.0 0.5]
rbc::vector expr {map($callback, x)}
rbc::vector expr {map([list adjust 2.0 0.5], x + 1)}
rbc::vector expr {map({apply {{value} {expr {$value * $value}}}}, x)}

A callback must return a real number or a two-element list of real numbers representing a complex value. If every result is scalar, the result vector is real, even when the input is complex. If any result is a pair (including a pair with zero imaginary part), the result vector is complex; scalar results are promoted with zero imaginary part. Empty input invokes no callbacks and returns an empty real vector. Callback results are converted to double precision; arbitrary expression strings, nonnumeric values, NaN, and infinities are rejected. A real destination still cannot store a complex expression result.

rbc::vector create z -type complex
z set {{3 4} {5 -12}}
proc conjugate {value} {
    lassign $value re im
    list $re [expr {-$im}]
}
rbc::vector expr {map(conjugate, z)}
# -> {3.0 -4.0} {5.0 12.0}
rbc::vector expr {map({apply {{value} {
    lassign $value re im
    expr {hypot($re, $im)}
}}}, z)}
# -> 5.0 13.0

Callbacks run in element order, starting at zero within the evaluated input (also for slices). An error stops iteration. Tcl callback errors retain their message and error code, with the element index added to the error stack. Invalid values and unexpected Tcl completion codes are errors too. Expression assignment occurs only after successful evaluation; callback side effects are not rolled back on failure.

The input expression is copied before callbacks start. Changing or destroying a source vector therefore does not change those captured input values. If a callback destroys the assignment destination, evaluation reports an error instead of assigning to a stale vector or a same-name replacement. Renaming the destination with rbc::vector rename retains its identity and permits the assignment to complete.

map invokes Tcl once per element and is intended for custom transformations and convenience. Built-in mathematical functions are faster for operations they already support. As with other function names, a vector literally named map(x) must be quoted as {map(x)} or "map(x)" when used as an expression operand.

Expression results

The result of vector expr is returned to the caller. Complex values are returned as {real imag} pairs:

vector expr {{1 2} + {3 -1}}
# -> {4.0 1.0}

The instance form stores the expression result directly in the vector:

x expr {x * 2}

A complex vector can store either a complex result or a real result; a real result is promoted to complex values with zero imaginary components.

A real vector cannot store a complex expression result:

vector create r
vector create c -type complex

c set {{1 2} {3 4}}
r expr {c + 1}
# error: can't store complex expression result in real vector

Fourier transforms

fft and ifft work with package require rbc::vector; Tk and an external FFT library are not required. The source can be real or complex. Destinations must already exist: use either one complex vector or two distinct real vectors. Transform commands never create vectors, interpret #auto as a request to create one, or change a vector’s type.

package require rbc::vector
rbc::vector create signal spectrumReal spectrumImag
rbc::vector create spectrum reconstructed -type complex
signal set {1 2 3 4}

# Complete complex spectrum, followed by an inverse transform.
signal fft spectrum
spectrum ifft reconstructed

# The same spectrum stored as separate real and imaginary components.
signal fft -real spectrumReal -imag spectrumImag
spectrumReal ifft -imaginput spectrumImag reconstructed

# Reuse the two real vectors as inverse destinations.
spectrumReal ifft -imaginput spectrumImag -real spectrumReal -imag spectrumImag

A positional destination must be complex, even when the input is real. -real and -imag must be supplied together and refer to different real vectors. Both commands also accept -imaginput for a real source; the supplied imaginary-input vector must be real and have the same length. All references use RBC vector lookup in the caller’s namespace, including literal names with parentheses. Partial vector ranges are rejected; copy a range into a vector first when a transform of a subset is required.

Input/output overlap is supported with matching types. For example, spectrum fft spectrum is valid for a complex vector; signal fft signal is an error when signal is real. Existing destination C entities, names, types, index offsets and client bindings are retained. Data pointers may change. C clients must reacquire data pointers after an update. Mapped-array caches and vector clients are updated as for other vector mutations. Both split results are installed before notifications are issued.

Length and zero padding

The initial implementation uses a radix-2 algorithm. The default transform length is the input length, which must be a positive power of two. A one-element transform is valid; an empty input is an error. -length auto pads with zeros to the next power of two. -length size requires a positive power of two at least as large as the input length. Smaller lengths are errors: input is never silently truncated. Both real and imaginary components are padded with zeros.

signal set {1 2 3}
signal fft spectrum -length auto
# spectrum has 4 bins.
spectrum ifft reconstructed
# reconstructed is approximately {{1 0} {2 0} {3 0} {0 0}}.

The inverse returns the transform length, including padding. The original unpadded length is not stored as vector metadata. ifft -length pads the supplied frequency-bin array before taking its inverse; it does not crop time-domain output or reconstruct an omitted negative-frequency half-spectrum.

Sign, scaling and frequency bins

For N samples, forward bin k is the sum of x[j] * exp(-2*pi*i*j*k/N) over j from 0 to N-1. The inverse uses the positive exponential and divides by N. Thus ifft(fft(x)) reproduces x up to floating-point rounding, or x followed by zeros if padding was requested. Neither transform applies a window, removes the mean, doubles bins, or performs amplitude, power or power-spectral-density scaling.

All N bins are returned in unshifted order: DC at index 0, positive frequencies next, then negative frequencies. For even N, index N/2 is the Nyquist bin. For uniform sample interval dt, indices below N/2 correspond to k/(N*dt) and indices above N/2 to (k-N)/(N*dt); the Nyquist bin represents the shared positive/negative Nyquist frequency. These are storage positions, independent of vector offsets. A real input has conjugate-symmetric bins; complex inputs generally do not.

rbc::vector create magnitude phase
magnitude expr {abs(spectrum)}
phase expr {arg(spectrum)}

These expressions give raw magnitude and phase in radians. Calibrated amplitude and power spectra need additional normalization. FFT assumes uniform sampling; resample adaptive simulation output before using it for a physical frequency spectrum. No sample-time vector or resampling is implicit in these commands.

Inputs must be finite. Non-finite input, non-finite results, invalid arguments, and temporary allocation failures return an error without replacing destination data. Runtime is O(N log N), with O(N) temporary storage. The operation runs synchronously and does not process Tcl events during computation.

Frequency-bin generation

Use an existing real vector to generate frequencies for a transform length N:

rbc::vector create frequencies
frequencies fftfreq 8 -delta 0.125
# {0 1 2 3 -4 -3 -2 -1}, in cycles per time unit.
frequencies fftfreq 8 -delta 0.125 -onesided true
# {0 1 2 3 4}

-delta is the finite, positive sample interval, defaulting to 1. Frequencies are in Hz when the interval is in seconds, or cycles per sample when it is 1. Full ordering, the default, matches the unshifted FFT: for even N it uses integer bins 0, ..., N/2-1, -N/2, ..., -1, and for odd N it uses 0, ..., (N-1)/2, -(N-1)/2, ..., -1. Each bin is divided by N and the sample interval.

The even-length Nyquist bin is negative in full ordering. -onesided true instead returns floor(N/2)+1 nonnegative bins, including positive Nyquist for even N. There is no Nyquist bin for odd N. Both forms return {0} for N=1. Any positive integer length is accepted, including non-powers of two.

Supply the actual transform length, including padding, rather than the number of original samples. This command only generates an axis: it neither computes nor truncates an FFT and it never doubles spectral amplitudes. Pair a one-sided axis with the first floor(N/2)+1 FFT bins. Such a reduced spectrum represents real signals through conjugate symmetry; it generally loses independent information from complex signals.

The frequency values follow the standard FFT bin convention documented by NumPy fftfreq.

Window generation

Generate coefficients in an existing real vector, then multiply the signal by that vector before padding:

rbc::vector create signal weights windowed frequencies magnitude positiveMagnitude
rbc::vector create spectrum -type complex
signal set {0 1 0 -1 0 1 0 -1}
weights window hann [signal length]
windowed expr {signal * weights}
windowed fft spectrum -length auto
frequencies fftfreq [spectrum length] -delta 0.001 -onesided true
magnitude expr {abs(spectrum)}
positiveMagnitude set [magnitude range 0 [expr {[spectrum length]/2}]]

window type length ?-periodic boolean? replaces the receiving vector with the requested coefficients. The types are rectangular, hann, hamming, blackman and bartlett. The default -periodic true is suitable for spectral analysis. -periodic false produces a symmetric window, suitable for filter design. For N>1 the periodic form is the first N coefficients of the symmetric N+1 window. Both forms accept any positive length. Every length-one window is {1}. Zero and negative lengths are errors.

For sample k from 0 to N-1, set D=N for periodic windows and D=N-1 for symmetric windows. For N>1:

  • Rectangular: w[k] = 1.

  • Hann: w[k] = 0.5 - 0.5*cos(2*pi*k/D).

  • Hamming: w[k] = 0.54 - 0.46*cos(2*pi*k/D).

  • Blackman: w[k] = 0.42 - 0.5*cos(2*pi*k/D) + 0.08*cos(4*pi*k/D).

  • Bartlett: w[k] = 1 - abs(2*k/D - 1).

These are the conventional window definitions; no extra normalization by sum, energy or sampled peak is applied. Some short or odd periodic windows, and even symmetric windows, do not contain a coefficient equal to 1. Symmetric Hann, Blackman and Bartlett windows have zero endpoints; the periodic form normally omits the final zero. Hamming endpoints are 0.08 in symmetric form.

The periodic/symmetric convention is also described in SciPy window documentation, that was used as a reference.

Window scaling

Windowing changes amplitude and energy. Use the coefficients applied to the original L samples to compute coherent gain sum(w)/L and energy sum(w*w). Padding does not add coefficients to these sums.

set coherentGain [rbc::vector expr {mean(weights)}]
set windowSum [rbc::vector expr {sum(weights)}]
set windowEnergy [rbc::vector expr {sum(weights * weights)}]

For an isolated complex tone exactly on a frequency bin, divide its unscaled FFT magnitude by windowSum to recover amplitude. Real-tone one-sided amplitude conventions additionally double the interior positive-frequency bins, but not DC or the even-length Nyquist bin. Leakage or overlapping components can still affect a tone’s measured amplitude.

A two-sided periodogram PSD convention is abs(X[k])^2 / (sampleRate * windowEnergy), where sampleRate = 1/delta. One-sided PSD for real signals doubles interior bins only. These are scaling conventions for scripts, not additional generator behavior. Check for zero sums or energy before division: for example, a symmetric two-sample Hann window is all zero.

Both generators preserve vector identity, type, index offset and client bindings, and return the qualified destination name. They replace all coefficients at once, refresh caches and notify clients. Invalid input, unrepresentable allocation sizes, allocation failures or non-finite frequency results leave data unchanged. Frequency underflow follows ordinary double arithmetic and may round extremely small bins to zero. The commands perform no implicit vector creation or resampling and do not interpret #auto.

C language API

Rbc vectors can be created, inspected, modified, and destroyed directly from C code. This is useful for Tcl extensions that generate or acquire large numeric data sets and want to make those values available to Tcl scripts or graph elements without converting every value to and from a Tcl list.

Include the Rbc public header:

#include <rbc.h>

Rbc exports its public C interface through the Rbc stubs table. An extension using the stubs interface should initialize it before calling Rbc functions:

static int InitRbcApi(Tcl_Interp *interp, const char *version) {
    if (Rbc_VectorInitStubs(interp, version, 0) == NULL) {
        return TCL_ERROR;
    }

    return TCL_OK;
}

version is the minimum Rbc package version required by the extension. Rbc_VectorInitStubs requires rbc::vector and initializes the existing Rbc stubs table without loading Tk. Rebuild the consumer with the updated rbcStubLib.c (or updated stub library) to use this new helper. The table layout and vector function slots are unchanged.

Rbc_InitStubs remains available and requires the full rbc package, including Tk and the toolbar. Both helpers use the same vector API and table; upgrading to the full package preserves vector pointers and subscriptions. As before, each interpreter must have its package initialized before using the API.

Vector representation

Rbc_Vector is an opaque public type:

typedef struct Rbc_Vector_s Rbc_Vector;

Applications must not access the internal vector structure directly. Use the public accessor and mutation functions described below.

Every vector has one of two numeric types:

typedef enum {
    RBC_VECTOR_REAL = 0,
    RBC_VECTOR_COMPLEX
} Rbc_VectorType;

Complex values use the portable public representation:

typedef struct {
    double real;
    double imag;
} Rbc_Complex;

Rbc_Complex deliberately does not use the native C complex type, so the public ABI does not depend on compiler-specific complex-number support.

Public vector functions

The following vector functions are exported through the Rbc stubs interface:

Function

Description

int Rbc_CreateVector(Tcl_Interp *interp, const char *vecName, Tcl_Size size, Rbc_Vector **vecPtrPtr)

Creates a real vector using the traditional Tcl command and automatic array mapping.

int Rbc_GetVector(Tcl_Interp *interp, const char *vecName, Rbc_Vector **vecPtrPtr)

Looks up an existing real or complex vector.

int Rbc_ResizeVector(Rbc_Vector *vecPtr, Tcl_Size nValues)

Changes the logical vector length while preserving its numeric type.

char *Rbc_NameOfVector(Rbc_Vector *vecPtr)

Returns the vector name. The returned string belongs to Rbc.

int Rbc_ResetVector(Rbc_Vector *vecPtr, double *dataArr, Tcl_Size nValues, Tcl_Size arraySize, Tcl_FreeProc *freeProc)

Replaces the storage of a real vector.

double *Rbc_VectorData(Rbc_Vector *vecPtr)

Returns real-vector storage, or NULL for a complex vector.

Tcl_Size Rbc_VectorLength(Rbc_Vector *vecPtr)

Returns the logical number of vector values.

Tcl_Size Rbc_VectorSize(Rbc_Vector *vecPtr)

Returns the current vector capacity in values.

int Rbc_VectorDirty(Rbc_Vector *vecPtr)

Returns the vector update counter.

int Rbc_VectorExists2(Tcl_Interp *interp, const char *vecName)

Tests whether a named vector exists.

void Rbc_FreeVector(Rbc_Vector *vecPtr)

Destroys the vector and releases its resources.

Rbc_VectorType Rbc_VectorGetType(Rbc_Vector *vecPtr)

Returns RBC_VECTOR_REAL or RBC_VECTOR_COMPLEX.

int Rbc_VectorGetRange(Rbc_Vector *vecPtr, double *minPtr, double *maxPtr)

Returns the minimum and maximum of a real vector. Returns TCL_ERROR for a complex vector.

int Rbc_CreateVectorWithType(Tcl_Interp *interp, const char *vecName, Tcl_Size size, Rbc_VectorType type, Rbc_Vector **vecPtrPtr)

Creates a real or complex vector of the requested type.

int Rbc_ResetComplexVector(Rbc_Vector *vecPtr, Rbc_Complex *dataArr, Tcl_Size nValues, Tcl_Size arraySize, Tcl_FreeProc *freeProc)

Replaces the storage of a complex vector.

Rbc_Complex *Rbc_VectorComplexData(Rbc_Vector *vecPtr)

Returns complex-vector storage, or NULL for a real vector.

void Rbc_VectorChanged(Rbc_Vector *vecPtr)

Reports that the existing vector storage was modified in place without changing its storage or ownership contract.

void Rbc_VectorChangedRange(Rbc_Vector *vecPtr, Tcl_Size first, Tcl_Size last)

Reports that the inclusive range first through last of existing vector storage was modified in place. Storage, length, capacity, type, and ownership remain unchanged.

Functions returning int as a Tcl status return TCL_OK on success and TCL_ERROR on failure. When an interpreter is available, an explanatory error message is left in its result.

Read-only state in C (since 0.8.1)

Function

Purpose

int Rbc_VectorIsReadOnly(Rbc_Vector *vecPtr)

Returns 0 or 1.

int Rbc_VectorSetReadOnly(Tcl_Interp *interp, Rbc_Vector *vecPtr, int readOnly)

Sets the state (nonzero means true); returns TCL_OK.

int Rbc_VectorCheckWritable(Tcl_Interp *interp, Rbc_Vector *vecPtr)

Returns TCL_OK, or TCL_ERROR with RBC VECTOR READONLY.

These functions are appended to the stubs table. Consumers using them must request at least 0.8.1 when calling Rbc_VectorInitStubs or Rbc_InitStubs. Existing slots and signatures are unchanged. State changes do not replace storage or send value-change notifications.

Resize, reset and recreation of a read-only destination fail before replacing storage. This includes same-length resize requests. Failed reset calls do not take ownership of the proposed buffer or call its free procedure. Rbc_FreeVector remains allowed regardless of the state.

Existing data accessors still return borrowed mutable pointers for compatibility. RBC cannot intercept writes through those pointers, including pointers retained before locking and externally owned storage. Native producers must check writability before modifying data, and again after any callbacks which may change the state. Readers need no such check. For example:

if (Rbc_VectorCheckWritable(interp, vecPtr) != TCL_OK) {
    return TCL_ERROR;
}
/* Here vecPtr is a real vector with at least one element. */
Rbc_VectorData(vecPtr)[0] = value;
Rbc_VectorChangedRange(vecPtr, 0, 0);

Rbc_VectorChanged and Rbc_VectorChangedRange remain notifications, not write permission checks. They report changes already made and must not be used to enforce protection after writing.

Vector lengths and capacities use Tcl_Size, allowing the C interface to represent Tcl 9-sized vectors.

Reading vector data

Rbc_GetVector obtains the opaque representation of an existing real or complex vector. Use Rbc_VectorGetType before selecting the storage accessor.

static int InspectVector(Tcl_Interp *interp, const char *name) {
    Rbc_Vector *vecPtr;
    Tcl_Size nValues;

    if (Rbc_GetVector(interp, name, &vecPtr) != TCL_OK) {
        return TCL_ERROR;
    }
    nValues = Rbc_VectorLength(vecPtr);
    switch (Rbc_VectorGetType(vecPtr)) {
    case RBC_VECTOR_REAL: {
        double *dataArr = Rbc_VectorData(vecPtr);
        /* Use dataArr[0] ... dataArr[nValues - 1]. */
        break;
    }
    case RBC_VECTOR_COMPLEX: {
        Rbc_Complex *dataArr = Rbc_VectorComplexData(vecPtr);
        /* Use dataArr[i].real and dataArr[i].imag. */
        break;
    }
    }
    return TCL_OK;
}

Storage returned by either accessor belongs to the vector and must not be freed by the caller. The pointer may become invalid after an operation that resizes, resets, or destroys the vector.

Updating vectors from C

Rbc_VectorData and Rbc_VectorComplexData return mutable storage. A caller may update that storage directly as long as no operation that can resize, replace, or destroy the vector is performed while the pointer is being used.

Direct writes do not themselves invalidate cached vector information or notify vector clients. After modifying existing storage in place, the caller must report the change with either:

Rbc_VectorChanged(vecPtr);

or:

Rbc_VectorChangedRange(vecPtr, first, last);

Rbc_VectorChangedRange should be preferred when the exact modified source interval is known.

first and last are zero-based inclusive source indices and must describe values already present in the vector. The operation reports a content change only; it does not resize the vector or change its storage ownership.

For example, a C extension updating part of a real waveform can write directly into the vector and then report exactly which samples changed:

static int UpdateSamples(Tcl_Interp *interp, const char *name, Tcl_Size first, Tcl_Size last, const double
    *values) {
    Rbc_Vector *vecPtr;
    double *dataArr;
    Tcl_Size nValues;
    Tcl_Size i;

    if (Rbc_GetVector(interp, name, &vecPtr) != TCL_OK) {
        return TCL_ERROR;
    }
    if (Rbc_VectorGetType(vecPtr) != RBC_VECTOR_REAL) {
        Tcl_SetObjResult(interp, Tcl_NewStringObj("vector must be real", -1));
        return TCL_ERROR;
    }
    nValues = Rbc_VectorLength(vecPtr);
    if ((first < 0) || (last < first) || (last >= nValues)) {
        Tcl_SetObjResult(interp, Tcl_NewStringObj("changed range is outside the vector", -1));
        return TCL_ERROR;
    }
    dataArr = Rbc_VectorData(vecPtr);
    for (i = first; i <= last; i++) {
        dataArr[i] = values[i - first];
    }
    Rbc_VectorChangedRange(vecPtr, first, last);
    return TCL_OK;
}

Supplying the changed range allows Rbc clients to preserve caches that are independent of the modified samples. For real vectors, Rbc also attempts to maintain the cached global minimum and maximum from the changed interval. If the modification touches information needed to determine an old global extremum, Rbc automatically falls back to a complete range calculation when required.

Graph line elements can use ranged Y-vector notifications to update only the affected part of a persistent display-decimation cache instead of rebuilding source-domain summaries for the complete vector.

Use Rbc_VectorChanged when the modified source interval is unknown, when changes cover effectively the whole vector, or when a caller does not need to provide more precise change information:

static int FillComplexVector(Tcl_Interp *interp, const char *name) {
    Rbc_Vector *vecPtr;
    Rbc_Complex *dataArr;
    Tcl_Size nValues;
    Tcl_Size i;

    if (Rbc_GetVector(interp, name, &vecPtr) != TCL_OK) {
        return TCL_ERROR;
    }
    if (Rbc_VectorGetType(vecPtr) != RBC_VECTOR_COMPLEX) {
        Tcl_SetObjResult(interp, Tcl_NewStringObj("vector must be complex", -1));
        return TCL_ERROR;
    }
    dataArr = Rbc_VectorComplexData(vecPtr);
    nValues = Rbc_VectorLength(vecPtr);
    for (i = 0; i < nValues; i++) {
        dataArr[i].real = (double)i;
        dataArr[i].imag = -(double)i;
    }
    Rbc_VectorChanged(vecPtr);
    return TCL_OK;
}

Both notification functions preserve the current data pointer, vector length, capacity, numeric type, and storage ownership policy. They flush the associated Tcl-array cache when required and notify registered vector clients.

Multiple modifications may be reported as one inclusive changed range when convenient. The range may therefore conservatively include unchanged samples; correctness does not require every value inside the reported interval to have actually changed.

Do not use Rbc_VectorChanged or Rbc_VectorChangedRange to report a resize or storage replacement. Use Rbc_ResizeVector, Rbc_ResetVector, or Rbc_ResetComplexVector for operations that change vector length, storage, or ownership.

Replacing vector storage

Rbc_ResetVector replaces the storage of a real vector. Rbc_ResetComplexVector performs the corresponding operation for a complex vector.

Calling the real reset function for a complex vector, or the complex reset function for a real vector, returns TCL_ERROR.

Both functions accept nValues, arraySize, and freeProc with the same ownership rules.

freeProc

Storage ownership

TCL_VOLATILE

Rbc copies the supplied data into its own storage. The caller retains ownership of dataArr and may reuse or release it immediately after the call returns.

TCL_STATIC

Rbc uses dataArr directly but never frees it. The caller must keep the array valid for as long as the vector uses it.

TCL_DYNAMIC

Rbc takes ownership of dataArr and releases it with Tcl’s allocator when the storage is replaced or the vector is destroyed. The array must therefore have been allocated with compatible Tcl allocation routines such as Tcl_Alloc.

custom Tcl_FreeProc *

Rbc takes ownership of dataArr and invokes the supplied deallocation procedure when the storage is no longer needed.

The ownership policy supplied to a reset operation becomes the vector’s storage contract. This is true even if the supplied data pointer is identical to the vector’s current data pointer.

Therefore use a reset function when installing or replacing storage. When only the contents of already-installed storage have been modified, use Rbc_VectorChangedRange if the modified interval is known, or Rbc_VectorChanged for an unknown/full-vector modification.

nValues specifies the logical number of values currently stored in the vector. arraySize specifies the capacity of the supplied array and must be greater than or equal to nValues.

Passing NULL for dataArr, or an arraySize of 0, resets the vector to an empty vector.

If arraySize is 0, a non-NULL dataArr is not adopted by the vector and ownership does not transfer to Rbc. The caller remains responsible for that storage.

For example, dynamically allocated storage can be transferred directly to a vector:

static int SetGeneratedData(Tcl_Interp *interp, const char *name) {
    Rbc_Vector *vecPtr;
    double *dataArr;
    Tcl_Size nValues;
    Tcl_Size i;

    nValues = 100;
    if (Rbc_GetVector(interp, name, &vecPtr) != TCL_OK) {
        return TCL_ERROR;
    }
    dataArr = (double *)Tcl_Alloc((size_t)nValues * sizeof(double));
    for (i = 0; i < nValues; i++) {
        dataArr[i] = (double)i * (double)i;
    }
    if (Rbc_ResetVector(vecPtr, dataArr, nValues, nValues, TCL_DYNAMIC) != TCL_OK) {
        Tcl_Free((char *)dataArr);
        return TCL_ERROR;
    }
    /*
     * Rbc now owns dataArr.
     * Do not free or reuse it here.
     */
    return TCL_OK;
}

When TCL_DYNAMIC is used, ownership transfers to Rbc only after a successful Rbc_ResetVector call.

Complex reset example:

static int SetComplexData(Tcl_Interp *interp, const char *name) {
    Rbc_Vector *vecPtr;
    Rbc_Complex values[2];

    if (Rbc_GetVector(interp, name, &vecPtr) != TCL_OK) {
        return TCL_ERROR;
    }

    values[0].real = 1.0;
    values[0].imag = 2.0;
    values[1].real = 3.0;
    values[1].imag = -4.0;

    return Rbc_ResetComplexVector(
        vecPtr, values, 2, 2, TCL_VOLATILE);
}

Creating vectors from C

Rbc_CreateVector is the compatibility interface for creating a real vector:

Rbc_Vector *realPtr;

if (Rbc_CreateVector(interp, "x", 100, &realPtr) != TCL_OK) {
    return TCL_ERROR;
}

To select the numeric type explicitly, use Rbc_CreateVectorWithType:

Rbc_Vector *complexPtr;

if (Rbc_CreateVectorWithType(interp, "z", 100, RBC_VECTOR_COMPLEX, &complexPtr) != TCL_OK) {
    return TCL_ERROR;
}

Both Rbc_CreateVector and Rbc_CreateVectorWithType accept literal names with balanced parentheses, including numeric names such as v(1); C constructors receive the length separately and never interpret name suffixes as sizes. Newly created real vectors receive an automatic Tcl array mapping only when their name contains no parentheses. Complex vectors never receive an automatic mapping. Rbc_CreateVector2 accepts a separate array name or NULL; explicit array names must not contain parentheses.

Public lookup, existence, client-token and deletion APIs accept these vector names without encoding. Lookup paths that accept ranges use the same exact-name precedence as Tcl vector references. No stubs ABI change or additional C function is required.

For example:

static int CreateTimeVector(Tcl_Interp *interp) {
    Rbc_Vector *vecPtr;
    double *dataArr;
    Tcl_Size nValues;
    Tcl_Size i;

    nValues = 1000;
    if (Rbc_CreateVector(interp, "time", nValues, &vecPtr) != TCL_OK) {
        return TCL_ERROR;
    }
    for (i = 0; i < nValues; i++) {
        dataArr[i] = (double)i * 1.0e-6;
    }
    Rbc_VectorChanged(vecPtr);
    return TCL_OK;
}

Directly modifying storage returned by Rbc_VectorData or Rbc_VectorComplexData does not itself invalidate cached values or notify vector clients. Call Rbc_VectorChangedRange after a known ranged modification, or Rbc_VectorChanged when the changed interval is unknown.

Use Rbc_ResetVector or Rbc_ResetComplexVector when replacing the vector’s storage or changing its ownership contract, not merely to announce that existing storage was modified.

Resizing a vector

Rbc_ResizeVector changes a vector’s logical length and notifies clients of the change.

Rbc_Vector *vecPtr;

if (Rbc_GetVector(interp, "data", &vecPtr) != TCL_OK) {
    return TCL_ERROR;
}
if (Rbc_ResizeVector(vecPtr, 10000) != TCL_OK) {
    return TCL_ERROR;
}

Values already in the vector are preserved when possible. When a vector is enlarged, newly created components are initialized to zero.

Testing for a vector

Rbc_VectorExists2 can be used when C code accepts either an existing vector or needs to create one:

Rbc_Vector *vecPtr;

if (Rbc_VectorExists2(interp, "data")) {
    if (Rbc_GetVector(interp, "data", &vecPtr) != TCL_OK) {
        return TCL_ERROR;
    }
} else {
    if (Rbc_CreateVector(interp, "data", 0, &vecPtr) != TCL_OK) {
        return TCL_ERROR;
    }
}

Destroying a vector

Rbc_FreeVector destroys a vector obtained from Rbc_GetVector or Rbc_CreateVector:

Rbc_Vector *vecPtr;

if (Rbc_GetVector(interp, "temporary", &vecPtr) != TCL_OK) {
    return TCL_ERROR;
}
Rbc_FreeVector(vecPtr);
/*
 * vecPtr is invalid from this point onward.
 */

Destroying the vector also removes its associated Tcl command and array mapping and releases vector storage according to the ownership policy established by Rbc_ResetVector.

Compatibility with older Rbc vector APIs

Older BLT and Rbc manuals describe additional C functions for vector client identifiers, change callbacks, deletion by name, and custom vector indices. Those routines are not part of the current public Rbc stubs interface and are intentionally not documented here.

Use the functions listed in Public vector functions when writing new extensions.


Copyright (c) George Yashin