Replaced Vector with Quantity
Some checks failed
Deploy MkDocs to Garage / build-and-deploy (push) Failing after 25s
Some checks failed
Deploy MkDocs to Garage / build-and-deploy (push) Failing after 25s
This commit is contained in:
parent
c350ffd1d6
commit
4b01dfe412
348
src/Quantity.zig
348
src/Quantity.zig
@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const hlp = @import("helper.zig");
|
||||
const Scales = @import("Scales.zig");
|
||||
const UnitScale = Scales.UnitScale;
|
||||
const Dimensions = @import("Dimensions.zig");
|
||||
const Dimension = Dimensions.Dimension;
|
||||
|
||||
@ -63,9 +64,6 @@ pub fn Quantity(
|
||||
return .{ .data = @splat(v) };
|
||||
}
|
||||
|
||||
/// Backward-compat alias used by Vector tests (`initDefault`).
|
||||
pub const initDefault = splat;
|
||||
|
||||
pub const zero: Self = splat(0);
|
||||
pub const one: Self = splat(1);
|
||||
|
||||
@ -185,7 +183,7 @@ pub fn Quantity(
|
||||
const rhs_q = rhs(r);
|
||||
const RhsType = @TypeOf(rhs_q);
|
||||
const SelfNorm = Quantity(T, N, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
||||
const RhsNorm = Quantity(T, N, RhsType.dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
||||
const RhsNorm = Quantity(T, RhsType.Len, RhsType.dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
||||
const l: Vec = if (comptime Self == SelfNorm) self.data else self.to(SelfNorm).data;
|
||||
const rr_base = if (comptime RhsType == RhsNorm) rhs_q else rhs_q.to(RhsNorm);
|
||||
const rr: Vec = broadcastToVec(RhsNorm, rr_base);
|
||||
@ -203,7 +201,7 @@ pub fn Quantity(
|
||||
const rhs_q = rhs(r);
|
||||
const RhsType = @TypeOf(rhs_q);
|
||||
const SelfNorm = Quantity(T, N, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
||||
const RhsNorm = Quantity(T, N, RhsType.dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
||||
const RhsNorm = Quantity(T, RhsType.Len, RhsType.dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
||||
const l: Vec = if (comptime Self == SelfNorm) self.data else self.to(SelfNorm).data;
|
||||
const rr_base = if (comptime RhsType == RhsNorm) rhs_q else rhs_q.to(RhsNorm);
|
||||
const rr: Vec = broadcastToVec(RhsNorm, rr_base);
|
||||
@ -286,16 +284,26 @@ pub fn Quantity(
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
/// Convert to a compatible quantity type. Dimension mismatch is a compile error.
|
||||
/// Dest can have the same Len as this quantity, or Len == 1 (in which case it
|
||||
/// will be automatically recast to this quantity's Len).
|
||||
/// The scale ratio is computed entirely at comptime; the only runtime cost is
|
||||
/// a SIMD multiply-by-splat (or element-wise cast for cross-numeric-type conversions).
|
||||
pub inline fn to(self: Self, comptime Dest: type) Dest {
|
||||
if (comptime !dims.eql(Dest.dims))
|
||||
@compileError("Dimension mismatch in to: " ++ dims.str() ++ " vs " ++ Dest.dims.str());
|
||||
if (comptime Self == Dest) return self;
|
||||
comptime std.debug.assert(Dest.Len == N);
|
||||
pub inline fn to(
|
||||
self: Self,
|
||||
comptime Dest: type,
|
||||
) Quantity(Dest.ValueType, N, Dest.dims.argsOpt(), Dest.scales.argsOpt()) {
|
||||
const ActualDest = Quantity(Dest.ValueType, N, Dest.dims.argsOpt(), Dest.scales.argsOpt());
|
||||
|
||||
const DestT = Dest.ValueType;
|
||||
const ratio = comptime (scales.getFactor(dims) / Dest.scales.getFactor(Dest.dims));
|
||||
if (comptime !dims.eql(ActualDest.dims))
|
||||
@compileError("Dimension mismatch in to: " ++ dims.str() ++ " vs " ++ ActualDest.dims.str());
|
||||
|
||||
if (comptime Self == ActualDest) return self;
|
||||
|
||||
// Allow Dest to be exactly matching Len or a Scalar (Len == 1)
|
||||
comptime std.debug.assert(Dest.Len == N or Dest.Len == 1);
|
||||
|
||||
const DestT = ActualDest.ValueType;
|
||||
const ratio = comptime (scales.getFactor(dims) / ActualDest.scales.getFactor(ActualDest.dims));
|
||||
const DestVec = @Vector(N, DestT);
|
||||
|
||||
// ── Same numeric type path ──
|
||||
@ -947,17 +955,305 @@ test "Vector initiate" {
|
||||
try std.testing.expect(m.data[0] == 1);
|
||||
}
|
||||
|
||||
// test "Format VectorX" {
|
||||
// const MeterPerSecondSq = Scalar(f32, .{ .L = 1, .T = -2 }, .{ .T = .n });
|
||||
// const KgMeterPerSecond = Scalar(f32, .{ .M = 1, .L = 1, .T = -1 }, .{ .M = .k });
|
||||
//
|
||||
// const accel = MeterPerSecondSq.Vec3.initDefault(9.81);
|
||||
// const momentum = KgMeterPerSecond.Vec3{ .data = .{ 43, 0, 11 } };
|
||||
//
|
||||
// var buf: [64]u8 = undefined;
|
||||
// var res = try std.fmt.bufPrint(&buf, "{d}", .{accel});
|
||||
// try std.testing.expectEqualStrings("(9.81, 9.81, 9.81)m.ns⁻²", res);
|
||||
//
|
||||
// res = try std.fmt.bufPrint(&buf, "{d:.2}", .{momentum});
|
||||
// try std.testing.expectEqualStrings("(43.00, 0.00, 11.00)m.kg.s⁻¹", res);
|
||||
// }
|
||||
test "Vector format" {
|
||||
const MeterPerSecondSq = Scalar(f32, .{ .L = 1, .T = -2 }, .{ .T = .n });
|
||||
const KgMeterPerSecond = Scalar(f32, .{ .M = 1, .L = 1, .T = -1 }, .{ .M = .k });
|
||||
|
||||
const accel = MeterPerSecondSq.Vec3.splat(9.81);
|
||||
const momentum = KgMeterPerSecond.Vec3{ .data = .{ 43, 0, 11 } };
|
||||
|
||||
var buf: [64]u8 = undefined;
|
||||
var res = try std.fmt.bufPrint(&buf, "{d}", .{accel});
|
||||
try std.testing.expectEqualStrings("(9.81, 9.81, 9.81)m.ns⁻²", res);
|
||||
|
||||
res = try std.fmt.bufPrint(&buf, "{d:.2}", .{momentum});
|
||||
try std.testing.expectEqualStrings("(43.00, 0.00, 11.00)m.kg.s⁻¹", res);
|
||||
}
|
||||
|
||||
test "Vector Vec3 Init and Basic Arithmetic" {
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const Vec3M = Meter.Vec3;
|
||||
|
||||
// Test zero, one, initDefault
|
||||
const v_zero = Vec3M.zero;
|
||||
try std.testing.expectEqual(0, v_zero.data[0]);
|
||||
try std.testing.expectEqual(0, v_zero.data[1]);
|
||||
try std.testing.expectEqual(0, v_zero.data[2]);
|
||||
|
||||
const v_one = Vec3M.one;
|
||||
try std.testing.expectEqual(1, v_one.data[0]);
|
||||
try std.testing.expectEqual(1, v_one.data[1]);
|
||||
try std.testing.expectEqual(1, v_one.data[2]);
|
||||
|
||||
const v_def = Vec3M.splat(5);
|
||||
try std.testing.expectEqual(5, v_def.data[0]);
|
||||
try std.testing.expectEqual(5, v_def.data[1]);
|
||||
try std.testing.expectEqual(5, v_def.data[2]);
|
||||
|
||||
// Test add and sub
|
||||
const v1 = Vec3M{ .data = .{ 10, 20, 30 } };
|
||||
const v2 = Vec3M{ .data = .{ 2, 4, 6 } };
|
||||
|
||||
const added = v1.add(v2);
|
||||
try std.testing.expectEqual(12, added.data[0]);
|
||||
try std.testing.expectEqual(24, added.data[1]);
|
||||
try std.testing.expectEqual(36, added.data[2]);
|
||||
|
||||
const subbed = v1.sub(v2);
|
||||
try std.testing.expectEqual(8, subbed.data[0]);
|
||||
try std.testing.expectEqual(16, subbed.data[1]);
|
||||
try std.testing.expectEqual(24, subbed.data[2]);
|
||||
|
||||
// Test negate
|
||||
const neg = v1.negate();
|
||||
try std.testing.expectEqual(-10, neg.data[0]);
|
||||
try std.testing.expectEqual(-20, neg.data[1]);
|
||||
try std.testing.expectEqual(-30, neg.data[2]);
|
||||
}
|
||||
|
||||
test "Vector Kinematics (Scalar Mul/Div)" {
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const Second = Scalar(i32, .{ .T = 1 }, .{});
|
||||
const Vec3M = Meter.Vec3;
|
||||
|
||||
const pos = Vec3M{ .data = .{ 100, 200, 300 } };
|
||||
const time = Second.splat(10);
|
||||
|
||||
// Vector divided by scalar (Velocity = Position / Time)
|
||||
const vel = pos.divScalar(time);
|
||||
try std.testing.expectEqual(10, vel.data[0]);
|
||||
try std.testing.expectEqual(20, vel.data[1]);
|
||||
try std.testing.expectEqual(30, vel.data[2]);
|
||||
try std.testing.expectEqual(1, @TypeOf(vel).dims.get(.L));
|
||||
try std.testing.expectEqual(-1, @TypeOf(vel).dims.get(.T));
|
||||
|
||||
// Vector multiplied by scalar (Position = Velocity * Time)
|
||||
const new_pos = vel.mulScalar(time);
|
||||
try std.testing.expectEqual(100, new_pos.data[0]);
|
||||
try std.testing.expectEqual(200, new_pos.data[1]);
|
||||
try std.testing.expectEqual(300, new_pos.data[2]);
|
||||
try std.testing.expectEqual(1, @TypeOf(new_pos).dims.get(.L));
|
||||
try std.testing.expectEqual(0, @TypeOf(new_pos).dims.get(.T));
|
||||
}
|
||||
|
||||
test "Vector Element-wise Math and Scaling" {
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const Vec3M = Meter.Vec3;
|
||||
|
||||
const v1 = Vec3M{ .data = .{ 10, 20, 30 } };
|
||||
const v2 = Vec3M{ .data = .{ 2, 5, 10 } };
|
||||
|
||||
// Element-wise division
|
||||
const div = v1.div(v2);
|
||||
try std.testing.expectEqual(5, div.data[0]);
|
||||
try std.testing.expectEqual(4, div.data[1]);
|
||||
try std.testing.expectEqual(3, div.data[2]);
|
||||
try std.testing.expectEqual(0, @TypeOf(div).dims.get(.L)); // M / M = Dimensionless
|
||||
}
|
||||
|
||||
test "Vector Conversions" {
|
||||
const KiloMeter = Scalar(i32, .{ .L = 1 }, .{ .L = .k });
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
|
||||
const v_km = KiloMeter.Vec3{ .data = .{ 1, 2, 3 } };
|
||||
const v_m = v_km.to(Meter);
|
||||
|
||||
try std.testing.expectEqual(1000, v_m.data[0]);
|
||||
try std.testing.expectEqual(2000, v_m.data[1]);
|
||||
try std.testing.expectEqual(3000, v_m.data[2]);
|
||||
|
||||
// Type checking the result
|
||||
try std.testing.expectEqual(1, @TypeOf(v_m).dims.get(.L));
|
||||
try std.testing.expectEqual(UnitScale.none, @TypeOf(v_m).scales.get(.L));
|
||||
}
|
||||
|
||||
test "Vector Length" {
|
||||
const MeterInt = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const MeterFloat = Scalar(f32, .{ .L = 1 }, .{});
|
||||
|
||||
// Integer length
|
||||
// 3-4-5 triangle on XY plane
|
||||
const v_int = MeterInt.Vec3{ .data = .{ 3, 4, 0 } };
|
||||
try std.testing.expectEqual(25, v_int.lengthSqr());
|
||||
try std.testing.expectEqual(5, v_int.length());
|
||||
|
||||
// Float length
|
||||
const v_float = MeterFloat.Vec3{ .data = .{ 3.0, 4.0, 0.0 } };
|
||||
try std.testing.expectApproxEqAbs(@as(f32, 25.0), v_float.lengthSqr(), 1e-4);
|
||||
try std.testing.expectApproxEqAbs(@as(f32, 5.0), v_float.length(), 1e-4);
|
||||
}
|
||||
|
||||
test "Vector Comparisons" {
|
||||
const Meter = Scalar(f32, .{ .L = 1 }, .{});
|
||||
const KiloMeter = Scalar(f32, .{ .L = 1 }, .{ .L = .k });
|
||||
|
||||
const v1 = Meter.Vec3{ .data = .{ 1000.0, 500.0, 0.0 } };
|
||||
const v2 = KiloMeter.Vec3{ .data = .{ 1.0, 0.5, 0.0 } };
|
||||
const v3 = KiloMeter.Vec3{ .data = .{ 1.0, 0.6, 0.0 } };
|
||||
|
||||
// 1. Equality (Whole vector)
|
||||
try std.testing.expect(v1.eqAll(v2));
|
||||
try std.testing.expect(v1.neAll(v3));
|
||||
|
||||
// 2. Element-wise Ordered Comparison
|
||||
const higher = v3.gt(v1); // compares 1km, 0.6km, 0km vs 1000m, 500m, 0m
|
||||
try std.testing.expectEqual(false, higher[0]); // 1km == 1000m
|
||||
try std.testing.expectEqual(true, higher[1]); // 0.6km > 500m
|
||||
try std.testing.expectEqual(false, higher[2]); // 0 == 0
|
||||
|
||||
// 3. Element-wise Equal Comparison
|
||||
const equal = v3.eq(v1); // compares 1km, 0.6km, 0km vs 1000m, 500m, 0m
|
||||
try std.testing.expectEqual(true, equal[0]); // 1km == 1000m
|
||||
try std.testing.expectEqual(false, equal[1]); // 0.6km > 500m
|
||||
try std.testing.expectEqual(true, equal[2]); // 0 == 0
|
||||
|
||||
// 3. Less than or equal
|
||||
const low_eq = v1.lte(v3);
|
||||
try std.testing.expect(low_eq[0] and low_eq[1] and low_eq[2]);
|
||||
}
|
||||
|
||||
test "Vector vs Scalar Comparisons" {
|
||||
const Meter = Scalar(f32, .{ .L = 1 }, .{});
|
||||
const KiloMeter = Scalar(f32, .{ .L = 1 }, .{ .L = .k });
|
||||
|
||||
const positions = Meter.Vec3{ .data = .{ 500.0, 1200.0, 3000.0 } };
|
||||
const threshold = KiloMeter.splat(1); // 1km (1000m)
|
||||
|
||||
// Check which axes exceed the 1km threshold
|
||||
const exceeded = positions.gtScalar(threshold);
|
||||
|
||||
try std.testing.expectEqual(false, exceeded[0]); // 500m > 1km is false
|
||||
try std.testing.expectEqual(true, exceeded[1]); // 1200m > 1km is true
|
||||
try std.testing.expectEqual(true, exceeded[2]); // 3000m > 1km is true
|
||||
|
||||
// Check for equality (broadcasted)
|
||||
const exact_match = positions.eqScalar(Meter.splat(500));
|
||||
try std.testing.expect(exact_match[0] == true);
|
||||
try std.testing.expect(exact_match[1] == false);
|
||||
}
|
||||
|
||||
test "Vector Dot and Cross Products" {
|
||||
const Meter = Scalar(f32, .{ .L = 1 }, .{});
|
||||
const Newton = Scalar(f32, .{ .M = 1, .L = 1, .T = -2 }, .{});
|
||||
|
||||
const pos = Meter.Vec3{ .data = .{ 10.0, 0.0, 0.0 } };
|
||||
const force = Newton.Vec3{ .data = .{ 5.0, 5.0, 0.0 } };
|
||||
|
||||
// 1. Dot Product (Work = F dot d)
|
||||
const work = force.dot(pos);
|
||||
try std.testing.expectEqual(50.0, work.value());
|
||||
// Dimensions should be M¹L²T⁻² (Energy/Joules)
|
||||
try std.testing.expectEqual(1, @TypeOf(work).dims.get(.M));
|
||||
try std.testing.expectEqual(2, @TypeOf(work).dims.get(.L));
|
||||
try std.testing.expectEqual(-2, @TypeOf(work).dims.get(.T));
|
||||
|
||||
// 2. Cross Product (Torque = r cross F)
|
||||
const torque = pos.cross(force);
|
||||
try std.testing.expectEqual(0.0, torque.data[0]);
|
||||
try std.testing.expectEqual(0.0, torque.data[1]);
|
||||
try std.testing.expectEqual(50.0, torque.data[2]);
|
||||
// Torque dimensions are same as Energy but as a Vector
|
||||
try std.testing.expectEqual(2, @TypeOf(torque).dims.get(.L));
|
||||
}
|
||||
|
||||
test "Vector Abs, Pow, Sqrt and Product" {
|
||||
const Meter = Scalar(f32, .{ .L = 1 }, .{});
|
||||
|
||||
const v1 = Meter.Vec3{ .data = .{ -2.0, 3.0, -4.0 } };
|
||||
|
||||
// 1. Abs
|
||||
const v_abs = v1.abs();
|
||||
try std.testing.expectEqual(2.0, v_abs.data[0]);
|
||||
try std.testing.expectEqual(4.0, v_abs.data[2]);
|
||||
|
||||
// 2. Product (L1 * L1 * L1 = L3)
|
||||
const vol = v_abs.product();
|
||||
try std.testing.expectEqual(24.0, vol.value());
|
||||
try std.testing.expectEqual(3, @TypeOf(vol).dims.get(.L));
|
||||
|
||||
// 3. Pow (Scalar exponent: (L1)^2 = L2)
|
||||
const area_vec = v_abs.pow(2);
|
||||
try std.testing.expectEqual(4.0, area_vec.data[0]);
|
||||
try std.testing.expectEqual(16.0, area_vec.data[2]);
|
||||
try std.testing.expectEqual(2, @TypeOf(area_vec).dims.get(.L));
|
||||
|
||||
// 4. Sqrt
|
||||
const sqrted = area_vec.sqrt();
|
||||
try std.testing.expectEqual(2, sqrted.data[0]);
|
||||
try std.testing.expectEqual(4, sqrted.data[2]);
|
||||
try std.testing.expectEqual(1, @TypeOf(sqrted).dims.get(.L));
|
||||
}
|
||||
|
||||
test "Vector mulScalar comptime_int" {
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const v = Meter.Vec3{ .data = .{ 1, 2, 3 } };
|
||||
|
||||
const scaled = v.mulScalar(10); // comptime_int → dimensionless
|
||||
try std.testing.expectEqual(10, scaled.data[0]);
|
||||
try std.testing.expectEqual(20, scaled.data[1]);
|
||||
try std.testing.expectEqual(30, scaled.data[2]);
|
||||
// Dimensions unchanged: L¹ × dimensionless = L¹
|
||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
||||
try std.testing.expectEqual(0, @TypeOf(scaled).dims.get(.T));
|
||||
}
|
||||
|
||||
test "Vector mulScalar comptime_float" {
|
||||
const MeterF = Scalar(f32, .{ .L = 1 }, .{});
|
||||
const v = MeterF.Vec3{ .data = .{ 1.0, 2.0, 4.0 } };
|
||||
|
||||
const scaled = v.mulScalar(0.5); // comptime_float → dimensionless
|
||||
try std.testing.expectApproxEqAbs(0.5, scaled.data[0], 1e-6);
|
||||
try std.testing.expectApproxEqAbs(1.0, scaled.data[1], 1e-6);
|
||||
try std.testing.expectApproxEqAbs(2.0, scaled.data[2], 1e-6);
|
||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
||||
}
|
||||
|
||||
test "Vector mulScalar T (value type)" {
|
||||
const MeterF = Scalar(f32, .{ .L = 1 }, .{});
|
||||
const v = MeterF.Vec3{ .data = .{ 3.0, 6.0, 9.0 } };
|
||||
const factor: f32 = 2.0;
|
||||
|
||||
const scaled = v.mulScalar(factor);
|
||||
try std.testing.expectApproxEqAbs(6.0, scaled.data[0], 1e-6);
|
||||
try std.testing.expectApproxEqAbs(12.0, scaled.data[1], 1e-6);
|
||||
try std.testing.expectApproxEqAbs(18.0, scaled.data[2], 1e-6);
|
||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
||||
}
|
||||
|
||||
test "Vector divScalar comptime_int" {
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const v = Meter.Vec3{ .data = .{ 10, 20, 30 } };
|
||||
|
||||
const halved = v.divScalar(2); // comptime_int → dimensionless divisor
|
||||
try std.testing.expectEqual(5, halved.data[0]);
|
||||
try std.testing.expectEqual(10, halved.data[1]);
|
||||
try std.testing.expectEqual(15, halved.data[2]);
|
||||
try std.testing.expectEqual(1, @TypeOf(halved).dims.get(.L));
|
||||
}
|
||||
|
||||
test "Vector divScalar comptime_float" {
|
||||
const MeterF = Scalar(f64, .{ .L = 1 }, .{});
|
||||
const v = MeterF.Vec3{ .data = .{ 9.0, 6.0, 3.0 } };
|
||||
|
||||
const r = v.divScalar(3.0);
|
||||
try std.testing.expectApproxEqAbs(3.0, r.data[0], 1e-9);
|
||||
try std.testing.expectApproxEqAbs(2.0, r.data[1], 1e-9);
|
||||
try std.testing.expectApproxEqAbs(1.0, r.data[2], 1e-9);
|
||||
try std.testing.expectEqual(1, @TypeOf(r).dims.get(.L));
|
||||
}
|
||||
|
||||
test "Vector eqScalar / gtScalar with comptime_int on dimensionless vector" {
|
||||
// Bare numbers are dimensionless, so comparisons only work when vector is dimensionless too.
|
||||
const DimLess = Scalar(i32, .{}, .{});
|
||||
const v = DimLess.Vec3{ .data = .{ 1, 2, 3 } };
|
||||
|
||||
const eq_res = v.eqScalar(2);
|
||||
try std.testing.expectEqual(false, eq_res[0]);
|
||||
try std.testing.expectEqual(true, eq_res[1]);
|
||||
try std.testing.expectEqual(false, eq_res[2]);
|
||||
|
||||
const gt_res = v.gtScalar(1);
|
||||
try std.testing.expectEqual(false, gt_res[0]);
|
||||
try std.testing.expectEqual(true, gt_res[1]);
|
||||
try std.testing.expectEqual(true, gt_res[2]);
|
||||
}
|
||||
|
||||
831
src/Vector.zig
831
src/Vector.zig
@ -1,831 +0,0 @@
|
||||
const std = @import("std");
|
||||
const hlp = @import("helper.zig");
|
||||
|
||||
const Scalar = @import("Quantity.zig").Scalar;
|
||||
const Scales = @import("Scales.zig");
|
||||
const UnitScale = Scales.UnitScale;
|
||||
const Dimensions = @import("Dimensions.zig");
|
||||
const Dimension = Dimensions.Dimension;
|
||||
|
||||
/// A fixed-size array of `len` elements sharing the same dimension and scale as scalar type `Q`.
|
||||
pub fn Vector(comptime len: usize, comptime Q: type) type {
|
||||
const T = Q.ValueType;
|
||||
|
||||
return struct {
|
||||
data: [len]T,
|
||||
|
||||
const Self = @This();
|
||||
pub const ScalarType = Q;
|
||||
pub const ValueType = T;
|
||||
pub const dims: Dimensions = Q.dims;
|
||||
pub const scales = Q.scales;
|
||||
|
||||
pub const zero = initDefault(0);
|
||||
pub const one = initDefault(1);
|
||||
|
||||
pub fn initDefault(v: T) Self {
|
||||
var data: [len]T = undefined;
|
||||
inline for (&data) |*item| item.* = v;
|
||||
return .{ .data = data };
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Internal: scalar-rhs normalisation (mirrors Scalar.zig)
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/// Resolved Scalar type for a scalar operand (bare number or Scalar).
|
||||
/// Passing another Vector here is a compile error.
|
||||
inline fn ScalarRhsT(comptime Rhs: type) type {
|
||||
if (comptime switch (@typeInfo(Rhs)) {
|
||||
.@"struct", .@"enum", .@"union", .@"opaque" => @hasDecl(Rhs, "ScalarType"),
|
||||
else => false,
|
||||
})
|
||||
@compileError(
|
||||
"Expected a Scalar or bare number; got a Vector. " ++
|
||||
"Use mul / div for element-wise vector operations.",
|
||||
);
|
||||
return hlp.rhsScalarType(T, Rhs);
|
||||
}
|
||||
|
||||
/// Normalise a scalar rhs (bare number → dimensionless Scalar).
|
||||
inline fn scalarRhs(r: anytype) ScalarRhsT(@TypeOf(r)) {
|
||||
return hlp.toRhsScalar(T, r);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Vector–Vector operations (rhs must be a Vector of the same length)
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/// Element-wise addition. Dimensions must match; scales resolve to the finer of the two.
|
||||
pub inline fn add(self: Self, rhs: anytype) Vector(len, Scalar(
|
||||
T,
|
||||
dims.argsOpt(),
|
||||
hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(),
|
||||
)) {
|
||||
const Tr = @TypeOf(rhs);
|
||||
var res: Vector(len, Scalar(
|
||||
T,
|
||||
dims.argsOpt(),
|
||||
hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(),
|
||||
)) = undefined;
|
||||
inline for (self.data, 0..) |v, i| {
|
||||
const q = (Q{ .value = v }).add(Tr.ScalarType{ .value = rhs.data[i] });
|
||||
res.data[i] = q.value;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Element-wise subtraction. Dimensions must match; scales resolve to the finer of the two.
|
||||
pub inline fn sub(self: Self, rhs: anytype) Vector(len, Scalar(
|
||||
T,
|
||||
dims.argsOpt(),
|
||||
hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(),
|
||||
)) {
|
||||
const Tr = @TypeOf(rhs);
|
||||
var res: Vector(len, Scalar(
|
||||
T,
|
||||
dims.argsOpt(),
|
||||
hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(),
|
||||
)) = undefined;
|
||||
inline for (self.data, 0..) |v, i| {
|
||||
const q = (Q{ .value = v }).sub(Tr.ScalarType{ .value = rhs.data[i] });
|
||||
res.data[i] = q.value;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Element-wise division. Dimension exponents are subtracted per component.
|
||||
pub inline fn div(
|
||||
self: Self,
|
||||
rhs: anytype,
|
||||
) Vector(len, Scalar(
|
||||
T,
|
||||
dims.sub(@TypeOf(rhs).dims).argsOpt(),
|
||||
hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(),
|
||||
)) {
|
||||
const Tr = @TypeOf(rhs);
|
||||
var res: Vector(len, Scalar(
|
||||
T,
|
||||
dims.sub(Tr.dims).argsOpt(),
|
||||
hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(),
|
||||
)) = undefined;
|
||||
inline for (self.data, 0..) |v, i| {
|
||||
const q = (Q{ .value = v }).div(Tr.ScalarType{ .value = rhs.data[i] });
|
||||
res.data[i] = q.value;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Element-wise multiplication. Dimension exponents are summed per component.
|
||||
pub inline fn mul(
|
||||
self: Self,
|
||||
rhs: anytype,
|
||||
) Vector(len, Scalar(
|
||||
T,
|
||||
dims.add(@TypeOf(rhs).dims).argsOpt(),
|
||||
hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(),
|
||||
)) {
|
||||
const Tr = @TypeOf(rhs);
|
||||
var res: Vector(len, Scalar(
|
||||
T,
|
||||
dims.add(Tr.dims).argsOpt(),
|
||||
hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(),
|
||||
)) = undefined;
|
||||
inline for (self.data, 0..) |v, i| {
|
||||
const q = (Q{ .value = v }).mul(Tr.ScalarType{ .value = rhs.data[i] });
|
||||
res.data[i] = q.value;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Vector–Scalar operations
|
||||
// scalar may be: Scalar, T, comptime_int, comptime_float
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/// Divide every component by a single scalar. Dimensions are subtracted.
|
||||
/// `scalar` may be a Scalar, `T`, `comptime_int`, or `comptime_float`.
|
||||
pub inline fn divScalar(
|
||||
self: Self,
|
||||
scalar: anytype,
|
||||
) Vector(len, Scalar(
|
||||
T,
|
||||
dims.sub(ScalarRhsT(@TypeOf(scalar)).dims).argsOpt(),
|
||||
hlp.finerScales(Self, ScalarRhsT(@TypeOf(scalar))).argsOpt(),
|
||||
)) {
|
||||
const s_norm = scalarRhs(scalar);
|
||||
const SN = @TypeOf(s_norm);
|
||||
var res: Vector(len, Scalar(
|
||||
T,
|
||||
dims.sub(SN.dims).argsOpt(),
|
||||
hlp.finerScales(Self, SN).argsOpt(),
|
||||
)) = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res.data[i] = (Q{ .value = v }).div(s_norm).value;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Multiply every component by a single scalar. Dimensions are summed.
|
||||
/// `scalar` may be a Scalar, `T`, `comptime_int`, or `comptime_float`.
|
||||
pub inline fn mulScalar(
|
||||
self: Self,
|
||||
scalar: anytype,
|
||||
) Vector(len, Scalar(
|
||||
T,
|
||||
dims.add(ScalarRhsT(@TypeOf(scalar)).dims).argsOpt(),
|
||||
hlp.finerScales(Self, ScalarRhsT(@TypeOf(scalar))).argsOpt(),
|
||||
)) {
|
||||
const s_norm = scalarRhs(scalar);
|
||||
const SN = @TypeOf(s_norm);
|
||||
var res: Vector(len, Scalar(
|
||||
T,
|
||||
dims.add(SN.dims).argsOpt(),
|
||||
hlp.finerScales(Self, SN).argsOpt(),
|
||||
)) = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res.data[i] = (Q{ .value = v }).mul(s_norm).value;
|
||||
return res;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Dot / Cross
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/// Standard dot product. Dimensions are summed (e.g., Force * Distance = Energy).
|
||||
/// Returns a Scalar type with the combined dimensions and finest scale.
|
||||
pub inline fn dot(self: Self, rhs: anytype) Scalar(
|
||||
T,
|
||||
dims.add(@TypeOf(rhs).dims).argsOpt(),
|
||||
hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(),
|
||||
) {
|
||||
const Tr = @TypeOf(rhs);
|
||||
|
||||
var sum: T = 0;
|
||||
inline for (self.data, 0..) |v, i| {
|
||||
const q_lhs = Q{ .value = v };
|
||||
const q_rhs = Tr.ScalarType{ .value = rhs.data[i] };
|
||||
sum += q_lhs.mul(q_rhs).value;
|
||||
}
|
||||
return .{ .value = sum };
|
||||
}
|
||||
|
||||
/// 3D Cross product. Dimensions are summed.
|
||||
/// Only valid for vectors of length 3.
|
||||
pub inline fn cross(self: Self, rhs: anytype) Vector(3, Scalar(
|
||||
T,
|
||||
dims.add(@TypeOf(rhs).dims).argsOpt(),
|
||||
hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(),
|
||||
)) {
|
||||
if (comptime len != 3)
|
||||
@compileError("Cross product is only defined for Vector(3, ...)");
|
||||
|
||||
const Tr = @TypeOf(rhs);
|
||||
const ResScalar = Scalar(T, dims.add(Tr.dims).argsOpt(), hlp.finerScales(Self, Tr).argsOpt());
|
||||
const ResVec = Vector(3, ResScalar);
|
||||
|
||||
// Calculation: [y1*z2 - z1*y2, z1*x2 - x1*z2, x1*y2 - y1*x2]
|
||||
const s1 = Q{ .value = self.data[0] };
|
||||
const s2 = Q{ .value = self.data[1] };
|
||||
const s3 = Q{ .value = self.data[2] };
|
||||
|
||||
const o1 = Tr.ScalarType{ .value = rhs.data[0] };
|
||||
const o2 = Tr.ScalarType{ .value = rhs.data[1] };
|
||||
const o3 = Tr.ScalarType{ .value = rhs.data[2] };
|
||||
|
||||
return ResVec{
|
||||
.data = .{
|
||||
s2.mul(o3).sub(s3.mul(o2)).value,
|
||||
s3.mul(o1).sub(s1.mul(o3)).value,
|
||||
s1.mul(o2).sub(s2.mul(o1)).value,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Unary
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/// Returns a vector where each component is the absolute value of the original.
|
||||
pub inline fn abs(self: Self) Self {
|
||||
var res: Self = undefined;
|
||||
inline for (self.data, 0..) |v, i| {
|
||||
const q = Q{ .value = v };
|
||||
res.data[i] = q.abs().value;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Returns a vector where each component is the absolute value of the original.
|
||||
pub inline fn sqrt(self: Self) Self {
|
||||
var res: Self = undefined;
|
||||
inline for (self.data, 0..) |v, i| {
|
||||
const q = Q{ .value = v };
|
||||
res.data[i] = q.sqrt().value;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Multiplies all components of the vector together.
|
||||
/// Resulting dimensions are (Original Dims * len).
|
||||
pub inline fn product(self: Self) Scalar(
|
||||
T,
|
||||
dims.scale(len).argsOpt(),
|
||||
scales.argsOpt(),
|
||||
) {
|
||||
var res_val: T = 1;
|
||||
if (comptime hlp.isInt(T)) {
|
||||
inline for (self.data) |v|
|
||||
res_val = res_val *| v;
|
||||
} else inline for (self.data) |v|
|
||||
res_val *= v;
|
||||
return .{ .value = res_val };
|
||||
}
|
||||
|
||||
/// Raises every component to a compile-time integer power.
|
||||
/// Dimensions are scaled by the exponent.
|
||||
pub inline fn pow(self: Self, comptime exp: comptime_int) Vector(
|
||||
len,
|
||||
Scalar(
|
||||
T,
|
||||
dims.scale(exp).argsOpt(),
|
||||
scales.argsOpt(),
|
||||
),
|
||||
) {
|
||||
const ResScalar = Scalar(T, dims.scale(exp).argsOpt(), scales.argsOpt());
|
||||
var res: Vector(len, ResScalar) = undefined;
|
||||
inline for (self.data, 0..) |v, i| {
|
||||
const q = Q{ .value = v };
|
||||
res.data[i] = q.pow(exp).value;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Negate all components. Dimensions are preserved.
|
||||
pub fn negate(self: Self) Self {
|
||||
var res: Self = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res.data[i] = -v;
|
||||
return res;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Conversion
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/// Convert all components to a compatible scalar type. Compile error on dimension mismatch.
|
||||
pub inline fn to(self: Self, comptime DestQ: type) Vector(len, DestQ) {
|
||||
var res: Vector(len, DestQ) = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res.data[i] = (Q{ .value = v }).to(DestQ).value;
|
||||
return res;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Length
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/// Sum of squared components. Cheaper than `length` — use for comparisons.
|
||||
pub inline fn lengthSqr(self: Self) T {
|
||||
var sum: T = 0;
|
||||
inline for (self.data) |v|
|
||||
sum += v * v;
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// Euclidean length. Integer types use integer sqrt (truncated).
|
||||
pub inline fn length(self: Self) T {
|
||||
const len_sq = self.lengthSqr();
|
||||
|
||||
if (comptime @typeInfo(T) == .int) {
|
||||
const UnsignedT = @Int(.unsigned, @typeInfo(T).int.bits);
|
||||
const u_len_sq = @as(UnsignedT, @intCast(len_sq));
|
||||
return @as(T, @intCast(std.math.sqrt(u_len_sq)));
|
||||
} else {
|
||||
return @sqrt(len_sq);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Vector–Vector comparisons
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/// Returns true only if all components are equal after scale resolution.
|
||||
pub inline fn eqAll(self: Self, rhs: anytype) bool {
|
||||
const Tr = @TypeOf(rhs);
|
||||
if (comptime !dims.eql(Tr.dims))
|
||||
@compileError("Dimension mismatch in eq: " ++ dims.str() ++ " vs " ++ Tr.dims.str());
|
||||
|
||||
inline for (self.data, 0..) |v, i| {
|
||||
const lhs_q = Q{ .value = v };
|
||||
const rhs_q = Tr.ScalarType{ .value = rhs.data[i] };
|
||||
if (!lhs_q.eq(rhs_q)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns true if any component differs after scale resolution.
|
||||
pub inline fn neAll(self: Self, rhs: anytype) bool {
|
||||
return !self.eqAll(rhs);
|
||||
}
|
||||
|
||||
/// Element-wise "Equal". Returns an array of booleans.
|
||||
pub inline fn eq(self: Self, rhs: anytype) [len]bool {
|
||||
const Tr = @TypeOf(rhs);
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).eq(Tr.ScalarType{ .value = rhs.data[i] });
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Element-wise "Not Equal". Returns an array of booleans.
|
||||
pub inline fn ne(self: Self, rhs: anytype) [len]bool {
|
||||
const Tr = @TypeOf(rhs);
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).ne(Tr.ScalarType{ .value = rhs.data[i] });
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Element-wise "Greater Than". Returns an array of booleans.
|
||||
pub inline fn gt(self: Self, rhs: anytype) [len]bool {
|
||||
const Tr = @TypeOf(rhs);
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).gt(Tr.ScalarType{ .value = rhs.data[i] });
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Element-wise "Greater Than or Equal". Returns an array of booleans.
|
||||
pub inline fn gte(self: Self, rhs: anytype) [len]bool {
|
||||
const Tr = @TypeOf(rhs);
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).gte(Tr.ScalarType{ .value = rhs.data[i] });
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Element-wise "Less Than". Returns an array of booleans.
|
||||
pub inline fn lt(self: Self, rhs: anytype) [len]bool {
|
||||
const Tr = @TypeOf(rhs);
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).lt(Tr.ScalarType{ .value = rhs.data[i] });
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Element-wise "Less Than or Equal". Returns an array of booleans.
|
||||
pub inline fn lte(self: Self, rhs: anytype) [len]bool {
|
||||
const Tr = @TypeOf(rhs);
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).lte(Tr.ScalarType{ .value = rhs.data[i] });
|
||||
return res;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Vector–Scalar comparisons
|
||||
// scalar may be: Scalar, T, comptime_int, comptime_float
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/// Compares every element in the vector to a single scalar for equality.
|
||||
/// Returns an array of booleans [len]bool. Dimensions must match; scales are auto-resolved.
|
||||
pub inline fn eqScalar(self: Self, scalar: anytype) [len]bool {
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).eq(scalar);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Compares every element in the vector to a single scalar for inequality.
|
||||
/// Returns an array of booleans [len]bool. Dimensions must match; scales are auto-resolved.
|
||||
pub inline fn neScalar(self: Self, scalar: anytype) [len]bool {
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).ne(scalar);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Checks if each element in the vector is strictly greater than the given scalar.
|
||||
/// Returns an array of booleans [len]bool.
|
||||
pub inline fn gtScalar(self: Self, scalar: anytype) [len]bool {
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).gt(scalar);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Checks if each element in the vector is greater than or equal to the given scalar.
|
||||
/// Returns an array of booleans [len]bool.
|
||||
pub inline fn gteScalar(self: Self, scalar: anytype) [len]bool {
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).gte(scalar);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Checks if each element in the vector is strictly less than the given scalar.
|
||||
/// Returns an array of booleans [len]bool.
|
||||
pub inline fn ltScalar(self: Self, scalar: anytype) [len]bool {
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).lt(scalar);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Checks if each element in the vector is less than or equal to the given scalar.
|
||||
/// Returns an array of booleans [len]bool.
|
||||
pub inline fn lteScalar(self: Self, scalar: anytype) [len]bool {
|
||||
var res: [len]bool = undefined;
|
||||
inline for (self.data, 0..) |v, i|
|
||||
res[i] = (Q{ .value = v }).lte(scalar);
|
||||
return res;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Formatting
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
pub fn formatNumber(
|
||||
self: Self,
|
||||
writer: *std.Io.Writer,
|
||||
options: std.fmt.Number,
|
||||
) !void {
|
||||
try writer.writeAll("(");
|
||||
for (self.data, 0..) |v, i| {
|
||||
if (i > 0) try writer.writeAll(", ");
|
||||
switch (@typeInfo(T)) {
|
||||
.float, .comptime_float => try writer.printFloat(v, options),
|
||||
.int, .comptime_int => try writer.printInt(v, 10, .lower, .{
|
||||
.width = options.width,
|
||||
.alignment = options.alignment,
|
||||
.fill = options.fill,
|
||||
.precision = options.precision,
|
||||
}),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
try writer.writeAll(")");
|
||||
var first = true;
|
||||
inline for (std.enums.values(Dimension)) |bu| {
|
||||
const v = dims.get(bu);
|
||||
if (comptime v == 0) continue;
|
||||
if (!first)
|
||||
try writer.writeAll(".");
|
||||
|
||||
first = false;
|
||||
|
||||
const uscale = scales.get(bu);
|
||||
if (bu == .T and (uscale == .min or uscale == .hour or uscale == .year))
|
||||
try writer.print("{s}", .{uscale.str()})
|
||||
else
|
||||
try writer.print("{s}{s}", .{ uscale.str(), bu.unit() });
|
||||
|
||||
if (v != 1)
|
||||
try hlp.printSuperscript(writer, v);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "Format VectorX" {
|
||||
const MeterPerSecondSq = Scalar(f32, .{ .L = 1, .T = -2 }, .{ .T = .n });
|
||||
const KgMeterPerSecond = Scalar(f32, .{ .M = 1, .L = 1, .T = -1 }, .{ .M = .k });
|
||||
|
||||
const accel = MeterPerSecondSq.Vec3.initDefault(9.81);
|
||||
const momentum = KgMeterPerSecond.Vec3{ .data = .{ 43, 0, 11 } };
|
||||
|
||||
var buf: [64]u8 = undefined;
|
||||
var res = try std.fmt.bufPrint(&buf, "{d}", .{accel});
|
||||
try std.testing.expectEqualStrings("(9.81, 9.81, 9.81)m.ns⁻²", res);
|
||||
|
||||
res = try std.fmt.bufPrint(&buf, "{d:.2}", .{momentum});
|
||||
try std.testing.expectEqualStrings("(43.00, 0.00, 11.00)m.kg.s⁻¹", res);
|
||||
}
|
||||
|
||||
test "VecX Init and Basic Arithmetic" {
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const Vec3M = Meter.Vec3;
|
||||
|
||||
// Test zero, one, initDefault
|
||||
const v_zero = Vec3M.zero;
|
||||
try std.testing.expectEqual(0, v_zero.data[0]);
|
||||
try std.testing.expectEqual(0, v_zero.data[1]);
|
||||
try std.testing.expectEqual(0, v_zero.data[2]);
|
||||
|
||||
const v_one = Vec3M.one;
|
||||
try std.testing.expectEqual(1, v_one.data[0]);
|
||||
try std.testing.expectEqual(1, v_one.data[1]);
|
||||
try std.testing.expectEqual(1, v_one.data[2]);
|
||||
|
||||
const v_def = Vec3M.initDefault(5);
|
||||
try std.testing.expectEqual(5, v_def.data[0]);
|
||||
try std.testing.expectEqual(5, v_def.data[1]);
|
||||
try std.testing.expectEqual(5, v_def.data[2]);
|
||||
|
||||
// Test add and sub
|
||||
const v1 = Vec3M{ .data = .{ 10, 20, 30 } };
|
||||
const v2 = Vec3M{ .data = .{ 2, 4, 6 } };
|
||||
|
||||
const added = v1.add(v2);
|
||||
try std.testing.expectEqual(12, added.data[0]);
|
||||
try std.testing.expectEqual(24, added.data[1]);
|
||||
try std.testing.expectEqual(36, added.data[2]);
|
||||
|
||||
const subbed = v1.sub(v2);
|
||||
try std.testing.expectEqual(8, subbed.data[0]);
|
||||
try std.testing.expectEqual(16, subbed.data[1]);
|
||||
try std.testing.expectEqual(24, subbed.data[2]);
|
||||
|
||||
// Test negate
|
||||
const neg = v1.negate();
|
||||
try std.testing.expectEqual(-10, neg.data[0]);
|
||||
try std.testing.expectEqual(-20, neg.data[1]);
|
||||
try std.testing.expectEqual(-30, neg.data[2]);
|
||||
}
|
||||
|
||||
test "VecX Kinematics (Scalar Mul/Div)" {
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const Second = Scalar(i32, .{ .T = 1 }, .{});
|
||||
const Vec3M = Meter.Vec3;
|
||||
|
||||
const pos = Vec3M{ .data = .{ 100, 200, 300 } };
|
||||
const time = Second{ .value = 10 };
|
||||
|
||||
// Vector divided by scalar (Velocity = Position / Time)
|
||||
const vel = pos.divScalar(time);
|
||||
try std.testing.expectEqual(10, vel.data[0]);
|
||||
try std.testing.expectEqual(20, vel.data[1]);
|
||||
try std.testing.expectEqual(30, vel.data[2]);
|
||||
try std.testing.expectEqual(1, @TypeOf(vel).dims.get(.L));
|
||||
try std.testing.expectEqual(-1, @TypeOf(vel).dims.get(.T));
|
||||
|
||||
// Vector multiplied by scalar (Position = Velocity * Time)
|
||||
const new_pos = vel.mulScalar(time);
|
||||
try std.testing.expectEqual(100, new_pos.data[0]);
|
||||
try std.testing.expectEqual(200, new_pos.data[1]);
|
||||
try std.testing.expectEqual(300, new_pos.data[2]);
|
||||
try std.testing.expectEqual(1, @TypeOf(new_pos).dims.get(.L));
|
||||
try std.testing.expectEqual(0, @TypeOf(new_pos).dims.get(.T));
|
||||
}
|
||||
|
||||
test "VecX Element-wise Math and Scaling" {
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const Vec3M = Meter.Vec3;
|
||||
|
||||
const v1 = Vec3M{ .data = .{ 10, 20, 30 } };
|
||||
const v2 = Vec3M{ .data = .{ 2, 5, 10 } };
|
||||
|
||||
// Element-wise division
|
||||
const div = v1.div(v2);
|
||||
try std.testing.expectEqual(5, div.data[0]);
|
||||
try std.testing.expectEqual(4, div.data[1]);
|
||||
try std.testing.expectEqual(3, div.data[2]);
|
||||
try std.testing.expectEqual(0, @TypeOf(div).dims.get(.L)); // M / M = Dimensionless
|
||||
}
|
||||
|
||||
test "VecX Conversions" {
|
||||
const KiloMeter = Scalar(i32, .{ .L = 1 }, .{ .L = .k });
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
|
||||
const v_km = KiloMeter.Vec3{ .data = .{ 1, 2, 3 } };
|
||||
const v_m = v_km.to(Meter);
|
||||
|
||||
try std.testing.expectEqual(1000, v_m.data[0]);
|
||||
try std.testing.expectEqual(2000, v_m.data[1]);
|
||||
try std.testing.expectEqual(3000, v_m.data[2]);
|
||||
|
||||
// Type checking the result
|
||||
try std.testing.expectEqual(1, @TypeOf(v_m).dims.get(.L));
|
||||
try std.testing.expectEqual(UnitScale.none, @TypeOf(v_m).scales.get(.L));
|
||||
}
|
||||
|
||||
test "VecX Length" {
|
||||
const MeterInt = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const MeterFloat = Scalar(f32, .{ .L = 1 }, .{});
|
||||
|
||||
// Integer length
|
||||
// 3-4-5 triangle on XY plane
|
||||
const v_int = MeterInt.Vec3{ .data = .{ 3, 4, 0 } };
|
||||
try std.testing.expectEqual(25, v_int.lengthSqr());
|
||||
try std.testing.expectEqual(5, v_int.length());
|
||||
|
||||
// Float length
|
||||
const v_float = MeterFloat.Vec3{ .data = .{ 3.0, 4.0, 0.0 } };
|
||||
try std.testing.expectApproxEqAbs(@as(f32, 25.0), v_float.lengthSqr(), 1e-4);
|
||||
try std.testing.expectApproxEqAbs(@as(f32, 5.0), v_float.length(), 1e-4);
|
||||
}
|
||||
|
||||
test "Vector Comparisons" {
|
||||
const Meter = Scalar(f32, .{ .L = 1 }, .{});
|
||||
const KiloMeter = Scalar(f32, .{ .L = 1 }, .{ .L = .k });
|
||||
|
||||
const v1 = Meter.Vec3{ .data = .{ 1000.0, 500.0, 0.0 } };
|
||||
const v2 = KiloMeter.Vec3{ .data = .{ 1.0, 0.5, 0.0 } };
|
||||
const v3 = KiloMeter.Vec3{ .data = .{ 1.0, 0.6, 0.0 } };
|
||||
|
||||
// 1. Equality (Whole vector)
|
||||
try std.testing.expect(v1.eqAll(v2));
|
||||
try std.testing.expect(v1.neAll(v3));
|
||||
|
||||
// 2. Element-wise Ordered Comparison
|
||||
const higher = v3.gt(v1); // compares 1km, 0.6km, 0km vs 1000m, 500m, 0m
|
||||
try std.testing.expectEqual(false, higher[0]); // 1km == 1000m
|
||||
try std.testing.expectEqual(true, higher[1]); // 0.6km > 500m
|
||||
try std.testing.expectEqual(false, higher[2]); // 0 == 0
|
||||
|
||||
// 3. Element-wise Equal Comparison
|
||||
const equal = v3.eq(v1); // compares 1km, 0.6km, 0km vs 1000m, 500m, 0m
|
||||
try std.testing.expectEqual(true, equal[0]); // 1km == 1000m
|
||||
try std.testing.expectEqual(false, equal[1]); // 0.6km > 500m
|
||||
try std.testing.expectEqual(true, equal[2]); // 0 == 0
|
||||
|
||||
// 3. Less than or equal
|
||||
const low_eq = v1.lte(v3);
|
||||
try std.testing.expect(low_eq[0] and low_eq[1] and low_eq[2]);
|
||||
}
|
||||
|
||||
test "Vector vs Scalar Comparisons" {
|
||||
const Meter = Scalar(f32, .{ .L = 1 }, .{});
|
||||
const KiloMeter = Scalar(f32, .{ .L = 1 }, .{ .L = .k });
|
||||
|
||||
const positions = Meter.Vec3{ .data = .{ 500.0, 1200.0, 3000.0 } };
|
||||
const threshold = KiloMeter{ .value = 1.0 }; // 1km (1000m)
|
||||
|
||||
// Check which axes exceed the 1km threshold
|
||||
const exceeded = positions.gtScalar(threshold);
|
||||
|
||||
try std.testing.expectEqual(false, exceeded[0]); // 500m > 1km is false
|
||||
try std.testing.expectEqual(true, exceeded[1]); // 1200m > 1km is true
|
||||
try std.testing.expectEqual(true, exceeded[2]); // 3000m > 1km is true
|
||||
|
||||
// Check for equality (broadcasted)
|
||||
const exact_match = positions.eqScalar(Meter{ .value = 500.0 });
|
||||
try std.testing.expect(exact_match[0] == true);
|
||||
try std.testing.expect(exact_match[1] == false);
|
||||
}
|
||||
|
||||
test "Vector Dot and Cross Products" {
|
||||
const Meter = Scalar(f32, .{ .L = 1 }, .{});
|
||||
const Newton = Scalar(f32, .{ .M = 1, .L = 1, .T = -2 }, .{});
|
||||
|
||||
const pos = Meter.Vec3{ .data = .{ 10.0, 0.0, 0.0 } };
|
||||
const force = Newton.Vec3{ .data = .{ 5.0, 5.0, 0.0 } };
|
||||
|
||||
// 1. Dot Product (Work = F dot d)
|
||||
const work = force.dot(pos);
|
||||
try std.testing.expectEqual(50.0, work.value);
|
||||
// Dimensions should be M¹L²T⁻² (Energy/Joules)
|
||||
try std.testing.expectEqual(1, @TypeOf(work).dims.get(.M));
|
||||
try std.testing.expectEqual(2, @TypeOf(work).dims.get(.L));
|
||||
try std.testing.expectEqual(-2, @TypeOf(work).dims.get(.T));
|
||||
|
||||
// 2. Cross Product (Torque = r cross F)
|
||||
const torque = pos.cross(force);
|
||||
try std.testing.expectEqual(0.0, torque.data[0]);
|
||||
try std.testing.expectEqual(0.0, torque.data[1]);
|
||||
try std.testing.expectEqual(50.0, torque.data[2]);
|
||||
// Torque dimensions are same as Energy but as a Vector
|
||||
try std.testing.expectEqual(2, @TypeOf(torque).dims.get(.L));
|
||||
}
|
||||
|
||||
test "Vector Abs, Pow, Sqrt and Product" {
|
||||
const Meter = Scalar(f32, .{ .L = 1 }, .{});
|
||||
|
||||
const v1 = Meter.Vec3{ .data = .{ -2.0, 3.0, -4.0 } };
|
||||
|
||||
// 1. Abs
|
||||
const v_abs = v1.abs();
|
||||
try std.testing.expectEqual(2.0, v_abs.data[0]);
|
||||
try std.testing.expectEqual(4.0, v_abs.data[2]);
|
||||
|
||||
// 2. Product (L1 * L1 * L1 = L3)
|
||||
const vol = v_abs.product();
|
||||
try std.testing.expectEqual(24.0, vol.value);
|
||||
try std.testing.expectEqual(3, @TypeOf(vol).dims.get(.L));
|
||||
|
||||
// 3. Pow (Scalar exponent: (L1)^2 = L2)
|
||||
const area_vec = v_abs.pow(2);
|
||||
try std.testing.expectEqual(4.0, area_vec.data[0]);
|
||||
try std.testing.expectEqual(16.0, area_vec.data[2]);
|
||||
try std.testing.expectEqual(2, @TypeOf(area_vec).dims.get(.L));
|
||||
|
||||
// 4. Sqrt
|
||||
const sqrted = area_vec.sqrt();
|
||||
try std.testing.expectEqual(2, sqrted.data[0]);
|
||||
try std.testing.expectEqual(4, sqrted.data[2]);
|
||||
try std.testing.expectEqual(2, @TypeOf(sqrted).dims.get(.L));
|
||||
}
|
||||
|
||||
test "mulScalar comptime_int" {
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const v = Meter.Vec3{ .data = .{ 1, 2, 3 } };
|
||||
|
||||
const scaled = v.mulScalar(10); // comptime_int → dimensionless
|
||||
try std.testing.expectEqual(10, scaled.data[0]);
|
||||
try std.testing.expectEqual(20, scaled.data[1]);
|
||||
try std.testing.expectEqual(30, scaled.data[2]);
|
||||
// Dimensions unchanged: L¹ × dimensionless = L¹
|
||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
||||
try std.testing.expectEqual(0, @TypeOf(scaled).dims.get(.T));
|
||||
}
|
||||
|
||||
test "mulScalar comptime_float" {
|
||||
const MeterF = Scalar(f32, .{ .L = 1 }, .{});
|
||||
const v = MeterF.Vec3{ .data = .{ 1.0, 2.0, 4.0 } };
|
||||
|
||||
const scaled = v.mulScalar(0.5); // comptime_float → dimensionless
|
||||
try std.testing.expectApproxEqAbs(0.5, scaled.data[0], 1e-6);
|
||||
try std.testing.expectApproxEqAbs(1.0, scaled.data[1], 1e-6);
|
||||
try std.testing.expectApproxEqAbs(2.0, scaled.data[2], 1e-6);
|
||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
||||
}
|
||||
|
||||
test "mulScalar T (value type)" {
|
||||
const MeterF = Scalar(f32, .{ .L = 1 }, .{});
|
||||
const v = MeterF.Vec3{ .data = .{ 3.0, 6.0, 9.0 } };
|
||||
const factor: f32 = 2.0;
|
||||
|
||||
const scaled = v.mulScalar(factor);
|
||||
try std.testing.expectApproxEqAbs(6.0, scaled.data[0], 1e-6);
|
||||
try std.testing.expectApproxEqAbs(12.0, scaled.data[1], 1e-6);
|
||||
try std.testing.expectApproxEqAbs(18.0, scaled.data[2], 1e-6);
|
||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
||||
}
|
||||
|
||||
test "divScalar comptime_int" {
|
||||
const Meter = Scalar(i32, .{ .L = 1 }, .{});
|
||||
const v = Meter.Vec3{ .data = .{ 10, 20, 30 } };
|
||||
|
||||
const halved = v.divScalar(2); // comptime_int → dimensionless divisor
|
||||
try std.testing.expectEqual(5, halved.data[0]);
|
||||
try std.testing.expectEqual(10, halved.data[1]);
|
||||
try std.testing.expectEqual(15, halved.data[2]);
|
||||
try std.testing.expectEqual(1, @TypeOf(halved).dims.get(.L));
|
||||
}
|
||||
|
||||
test "divScalar comptime_float" {
|
||||
const MeterF = Scalar(f64, .{ .L = 1 }, .{});
|
||||
const v = MeterF.Vec3{ .data = .{ 9.0, 6.0, 3.0 } };
|
||||
|
||||
const r = v.divScalar(3.0);
|
||||
try std.testing.expectApproxEqAbs(3.0, r.data[0], 1e-9);
|
||||
try std.testing.expectApproxEqAbs(2.0, r.data[1], 1e-9);
|
||||
try std.testing.expectApproxEqAbs(1.0, r.data[2], 1e-9);
|
||||
try std.testing.expectEqual(1, @TypeOf(r).dims.get(.L));
|
||||
}
|
||||
|
||||
test "eqScalar / gtScalar with comptime_int on dimensionless vector" {
|
||||
// Bare numbers are dimensionless, so comparisons only work when vector is dimensionless too.
|
||||
const DimLess = Scalar(i32, .{}, .{});
|
||||
const v = DimLess.Vec3{ .data = .{ 1, 2, 3 } };
|
||||
|
||||
const eq_res = v.eqScalar(2);
|
||||
try std.testing.expectEqual(false, eq_res[0]);
|
||||
try std.testing.expectEqual(true, eq_res[1]);
|
||||
try std.testing.expectEqual(false, eq_res[2]);
|
||||
|
||||
const gt_res = v.gtScalar(1);
|
||||
try std.testing.expectEqual(false, gt_res[0]);
|
||||
try std.testing.expectEqual(true, gt_res[1]);
|
||||
try std.testing.expectEqual(true, gt_res[2]);
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Vector = @import("Vector.zig").Vector;
|
||||
pub const Vector = @import("Quantity.zig").Vector;
|
||||
pub const Scalar = @import("Quantity.zig").Scalar;
|
||||
pub const Dimensions = @import("Dimensions.zig");
|
||||
pub const Scales = @import("Scales.zig");
|
||||
pub const Base = @import("Base.zig");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user