Unit bearing arrays
Preserve temperature and distance units in packed native Array, DynamicArray and NDArray calculations, statistics, conversions and research workflows.
Import aner.units to use Array<Quantity<m>>, DynamicArray<Point<degC>> or NDArray<Quantity<"m/s">>. Each container stores contiguous native Float64 magnitudes and one immutable unit descriptor. Its checked element type retains both the physical unit and the distinction between a quantity and an absolute temperature point.
These features require the updated native development runtime built from this source and matching editor support. Earlier packaged executables may reject these element types. This guide does not announce a released binary or a new timing benchmark. See installation and scalar units for the existing literal syntax and 23 code registry.
Choose a container
| Need | Type | Construction |
|---|---|---|
| Fixed one dimensional distances | Array<Quantity<m>> | Array<Quantity<m>>(size: 3, fill: 0.0@m) |
| Growable absolute temperature readings | DynamicArray<Point<degC>> | DynamicArray<Point<degC>>(capacity: 4) |
| Contiguous shaped speeds | NDArray<Quantity<"m/s">> | NDArray<Quantity<"m/s">>(shape: List(2, 3), fill: 0.0@"m/s") |
The existing array constructors, shape rules, mutation and ownership contracts apply. This adds element specializations to the built in containers; there is no deeper module such as aner.units.array to import. Primitive arrays still require no import. Unit annotations and literals require aner.units.
The Array, DynamicArray and NDArray references cover family specific methods, growth, sorting, reshape, bounds and resource accounting. A container has one exact unit, not a different unit descriptor for every element.
Construction and exact writes
import aner.units
let lengths: Array<Quantity<m>> = Array<Quantity<m>>(size: 3, fill: 0.0@m)
lengths[0] = 1.0@m
lengths.set(index: 1, value: (200.0@cm).to("m"))
lengths[2] = 3.0@m
print(lengths.mean())
print(lengths.sum())
print(type(lengths))
print(dtype(lengths))The output is 2.0@m, 6.0@m, Array<Quantity<m>> and Float64. A magnitude remains Float64 while its element type remains Quantity<m>. The fill value, set, bracket assignment, append, insert, resize fill and search arguments must have the exact declared element type. Centimeters must be explicitly converted before storage in a meter array. Raw Float64 and Int64 values are not implicitly attached to a unit.
The following forms are rejected during checking:
lengths[0] = 100.0@cm // Compatible dimension, but the stored type is Quantity<m>.
lengths[0] = 1.0 // No explicit source unit.
lengths[0] = 1@m // A unit literal requires a Float64 magnitude.For raw imported magnitudes use units.quantity(value, "m") or units.point(value, "degC") with a literal registered code. Construction attaches the declared source unit; .to performs conversion. Array assignment, casts and function parameters never perform implicit unit conversion.
Bracket access returns the unit bearing scalar. Indexed compound updates use the scalar rules and require the final result to have the same exact element type: a meter cell can add a compatible distance, and a Celsius point cell can add a compatible interval. Subtracting a Point from a Point cell produces a Quantity and cannot be written back to that Point cell. See scalar arithmetic and bracket evaluation for source order and failure behavior.
Inspect, convert and export
| Method or inspection | Result | Contract |
|---|---|---|
type(array) or type_of(array) | String | Full family and element specialization, such as NDArray<Quantity<"m/s">>. |
dtype(array) | String | Float64, the packed magnitude storage type. |
array.unit() | String | Exact declared unit code; does not scan elements. |
array.get(...) or brackets | Quantity<U> or Point<U> | Scalar with the array's exact unit and kind. |
array.to(target_unit: String) | Same family with target element unit | Fresh converted buffer; target must be a literal String and have a compatible dimension and kind. |
array.values() | Same family of primitive Float64 | Fresh buffer containing magnitudes in the current unit; explicitly removes the unit from the returned values. |
array.to_list() | List | Copied scalar values retain their units. |
Conversion preserves the shape or live sequence and creates independent storage, including conversion to the same unit. Point conversion applies scale and offset; quantity and interval conversion applies scale alone. A runtime variable holding "m" cannot determine the checked result type of .to; supply a literal code.
import aner.units
let original = Array<Quantity<m>>(size: 2, fill: 1.0@m)
let centimeters = original.to("cm")
print(centimeters.get(0))
let same_unit = original.to("m")
same_unit[0] = 99.0@m
print(original.get(0))
let raw = original.values()
raw[0] = -99.0
print(original.get(0))This prints 100.0@cm, then 1.0@m twice. .values() is an explicit interoperability boundary, not a shared writable view. Record original.unit() with the exported magnitudes. Direct .to_float() and .to_tensor() are unavailable on a unit bearing array; they would discard its checked unit implicitly.
A primitive export can use the existing Tensor bridge: one dimensional arrays call array.values().to_tensor(rows: ..., cols: ...); a positive rank two NDArray calls array.values().to_tensor(). The Tensor contains numerical magnitudes without the physical unit. Keep units, class mappings, row meanings and any applicable scientific interpretation with the consuming model. Neither a unit array nor this export automatically supplies coordinate identity or measurement uncertainty.
Import a CSV column with its unit
CSV unit columns in aner.data declare one source unit and explicit nullability for a numerical column. scan.convert_unit optionally normalizes every present source value before filtering/projection. A complete table column can be extracted as table.unit_values("distance_cm", "m"), returning a fresh Array<Quantity<m>>; temperature extraction can return Array<Point<degC>>. Literal accessor targets determine these exact element types.
Missing cells are not silently compacted into a shorter array. Apply where_present explicitly when the research population should exclude them, retaining text IDs and source order alongside the measurements. Typed retrieval and array arithmetic preserve units; deliberate .values() export remains the raw numerical boundary. The CSV research source carries normalized columns into Point statistics, interval anomalies and distance/time speeds.
Named array arithmetic
Use .add, .sub, .mul, .div, .scale, .dot and .matmul; this feature does not introduce array infix arithmetic. Binary operands must belong to the same array family and satisfy the operation's physical rule. Array and DynamicArray require equal live lengths. NDArray keeps its existing checked trailing axis singleton broadcasting.
| Elementwise operation | Result element type |
|---|---|
Quantity .add or .sub compatible Quantity | Quantity in the left operand's unit. |
Point .add or .sub compatible interval Quantity | Point in the left operand's unit. |
Interval Quantity .add compatible Point | Point in the right operand's unit. |
Point .sub compatible Point | Quantity in the left point's corresponding difference unit. |
Quantity .mul or .div Quantity | Quantity in a canonical registered unit for the resulting dimension. |
Point .add Point; interval .sub Point | Rejected affine arithmetic. |
Point .mul, .div or .scale | Rejected affine arithmetic. |
The same shared rules govern scalar and array operations. Compatible addition/subtraction converts magnitudes without changing the input buffers. Products and ratios normalize to a scale one registered unit, such as m/s, J, W, N or 1, when its dimension is registered. Array results have independent storage.
The registry remains bounded to 23 codes. It does not contain square meters, square seconds or squared temperature units. Array<Quantity<m>>.mul(Array<Quantity<m>>) and a meter by meter dot product therefore fail checking even though primitive Float64 multiplication is available. Quoted codes such as "m/s" identify a fixed registered unit; they do not enable arbitrary unit expression parsing.
Means, spreads and reductions
| Reduction | Quantity elements | Point elements |
|---|---|---|
min(), max() | Quantity in the declared unit. | Point in the declared unit. |
mean() | Quantity in the declared unit. | Point in the declared unit: an affine average. |
std() | Quantity in the declared unit. | Quantity in the corresponding interval unit. |
sum() | Quantity in the declared unit. | Unavailable: absolute points cannot be summed. |
One dimensional cumsum() | Same family and unit. | Unavailable. |
variance() | Quantity in a canonical registered squared unit, when available. | Unavailable with the current registry. |
argmin(), argmax() | Existing Int64 index contract. | Existing Int64 index contract. |
Statistics use the population definition, dividing by the element count. NDArray reductions cover all elements; argmin and argmax return flat row major indices. NDArray does not gain cumsum. A standard deviation for Point<degC> returns Quantity<delta_degC>, never an absolute Celsius point. Fahrenheit points produce Fahrenheit intervals; conversion of those intervals applies no point offset.
Only dimensionless Quantity<"1"> currently has a registered squared dimension for variance. Thus its variance returns Quantity<"1">; variance for lengths, speeds and temperature readings is unavailable instead of inventing a new unit descriptor. Standard deviation can remain supported when variance's unit is unavailable.
Empty quantity sums, one dimensional dot products and cumulative results retain their existing typed zero/empty behavior when the result unit is admitted. Mean, standard deviation, extrema and their indices require nonempty input. Point sums remain unavailable even for an empty container. No ignored missing value or sample statistic mode is introduced.
Observed population spread is different from a measurement uncertainty model. A uniform declared calibration offset changes the temperature mean without changing that observed spread. Unit arrays alone do not specify independent noise, shared calibration uncertainty or cross observation covariance. Use aner.science and EvidenceTensor when those source relationships matter.
Temperature calibration and interval anomalies
The complete research program includes all four synthetic Fahrenheit readings: 68.0, 71.6, 75.2 and 78.8 degF. It converts them to Celsius, adds a declared correction of 0.5 delta_degC, then compares them with a 23.5 degC reference.
The converted readings are approximately 20, 22, 24 and 26 degC. The corrected mean is 23.5 degC, or 296.65 K. Population standard deviation is sqrt(5) delta_degC. Subtracting the reference produces approximately −3, −1, 1 and 3 delta_degC, whose sum is zero. Float64 tolerance checks accommodate conversion roundoff; the example does not infer uncertainty from those synthetic values.
Point normalization, correction and subtraction retain their checked types:
import aner.units
let readings = DynamicArray<Point<degF>>(capacity: 2)
readings.append(68.0@degF)
readings.append(71.6@degF)
let celsius = readings.to("degC")
let correction = DynamicArray<Quantity<delta_degC>>(capacity: 2)
correction.resize(size: 2, fill: 0.5@delta_degC)
let calibrated: DynamicArray<Point<degC>> = celsius.add(correction)
print(calibrated.mean())
print(calibrated.std().unit())The mean is approximately 21.5 degC and the spread unit is delta_degC. The exact printable mean follows native Float64 conversion. Point addition to another Point, Point scaling and Point cumulative sums are deliberately absent.
Distances, speeds and matrix products
The research program's travel matrix has two device rows and three ordered run columns. Distances are [100, 120, 140] and [80, 100, 120] centimeters. A 1 × 3 time row contains [2, 4, 5] seconds. NDArray division broadcasts that time row across devices and yields a 2 × 3 Quantity<"m/s"> result.
The first row's speeds are approximately 0.5, 0.3 and 0.28 m/s; the second row's are 0.4, 0.25 and 0.24 m/s. A 3 × 1 matrix of dimensionless one third weights produces mean speeds of approximately 0.36 and 0.2966666666666667 m/s using .matmul. Speed multiplied by dimensionless weight has a registered canonical speed unit.
One dimensional .dot and rank two NDArray .matmul require Quantity elements and a registered canonical product unit. Point operands are unavailable. A force/distance dot product can return Quantity<J>; a length/length dot product requires an unregistered squared length and is rejected. Matrix shape, work limits and source order accumulation follow the existing NDArray contract; matmul does not broadcast batch axes.
The array shape records positions, not named scientific coordinates. Preserve device/run order explicitly as this fixture does. Use SemanticTensor for coordinate alignment contracts or AdaptiveFrame for typed observation identities and roles.
Native layout, ownership and complexity
Each array buffer stores Float64 magnitudes in eight byte contiguous native slots plus one shared immutable unit descriptor. It does not store a Quantity/Point object, owned unit String or Any tag per element. Reading a cell reconstructs a unit bearing scalar; copying or reshaping retains the declared unit. No Tensor wrapper or per element object graph is introduced. This is a layout contract, not a measured speed claim.
Assignments alias the same mutable descriptor. .copy() creates independent storage. Fixed Array/NDArray reshape shares its buffer and unit; DynamicArray reshape copies live elements into an independent fixed buffer. NDArray flatten, transpose and permutation make independent results. Unit conversion and .values() also make independent buffers, including same unit conversion.
Let n be live element count, C DynamicArray capacity, r NDArray rank and N broadcast output size. Unit lookup and arithmetic admission use the fixed size registry.
| Operation | Time | Additional storage |
|---|---|---|
unit(), type, dtype | Constant bounded metadata work | Bounded returned text. |
| Scalar get/set | O(1) in one dimension; O(r) for NDArray | Scalar result or validated coordinate metadata. |
.to, .values, one dimensional binary methods or cumulative sum | O(n); ND metadata adds O(r) | O(n) fresh result plus metadata. |
| Quantity/Point reductions | O(n) | O(1) auxiliary work. |
| One dimensional dot | O(n) | O(1) auxiliary work. |
| NDArray broadcast arithmetic | O(N + r) | O(N) result and O(r) traversal metadata. |
NDArray matmul [m, k] × [k, p] | O(m · k · p + m · p) | O(m · p) result. |
| Fixed reshape | O(r) | Shared buffer, O(r) descriptor metadata. |
| DynamicArray growth | Existing amortized append bound; one growth can allocate/copy capacity | Old and replacement buffers can coexist transiently. |
Existing safety limits remain: 1,000,000 array slots, rank 1 through 8, a shared 64 MiB logical array buffer budget and 50,000,000 NDArray matrix multiply adds. These are logical bounds, not total process memory limits. Primitive numerical exports and Tensor bridges allocate additional storage with their own limits. Unit arrays remain mutable CPU buffers without automatic differentiation or native GPU kernels.
Diagnostics and complete examples
Unknown unit codes, incompatible unit element types, dynamic conversion targets, invalid affine arithmetic and unavailable methods fail checking. Static type/unit incompatibilities generally use E1002; unavailable methods use E1001. Array runtime failures use located R3001, including bounds, shapes, allocation limits and native numerical failures. A scalar literal or factory evaluated before entering an array operation can fail with scalar unit diagnostic R3701.
Inputs and results must be finite. Unit arithmetic preserves the shared checked dimension, affine kind and representable range rules. Native failures do not publish a partial arithmetic result or silently remove a unit. Mutable array and persistent session failure behavior remains documented in the family references. No exact decimal or cross platform bitwise conversion promise is made.
Both complete examples have local synthetic inputs and require no server, credentials or private settings:
- Unit array basics: exact writes, unit inspection, typed reductions, independent conversions and exports, temperatures and speed broadcasting.
- Calibration and travel research: Fahrenheit normalization, declared temperature correction, interval anomalies, dimensionally checked speed calculation, weighted matrix means and explicit Tensor export.
aner check examples/unit_arrays_basic.aner
aner run examples/unit_arrays_basic.aner
aner check examples/unit_arrays_research.aner
aner run examples/unit_arrays_research.anerThe research example prints 20 named checks, each followed by true when its documented mathematical and ownership contracts hold. Its tolerance comparisons are explicit; they do not stop the interpreter like an assertion. Use the exact fixture and updated development runtime to reproduce the results. Automated native verification and publication status are established separately from this guide's source edits.