Changed how Scalar is generated to use Dimensions.ArgOpts and Scales.ArgOpts

This commit is contained in:
adrien 2026-04-22 16:31:07 +02:00
parent a2d46e3f55
commit 82bdb96746
5 changed files with 174 additions and 128 deletions

View File

@ -5,22 +5,32 @@ const Dimensions = @import("Dimensions.zig");
const Scales = @import("Scales.zig");
const Scalar = @import("Scalar.zig").Scalar;
/// Helper function to create a clean namespace for each physical dimension.
/// It exposes the raw dimensions, and easy type-creators for Base or Scaled variants.
pub fn BaseScalar(comptime d: anytype) type {
fn PhysicalConstant(comptime d: Dimensions.ArgOpts, comptime val: f64, comptime s: Scales.ArgOpts) type {
return struct {
pub const dims = Dimensions.init(d);
const dims = Dimensions.init(d);
const scales = Scales.init(s);
/// Instantiates the constant into a specific numeric type.
pub fn Of(comptime T: type) Scalar(T, d, s) {
return .{ .value = @as(T, @floatCast(val)) };
}
};
}
fn BaseScalar(comptime d: Dimensions.ArgOpts) type {
return struct {
const dims = Dimensions.init(d);
/// Creates a Scalar of this dimension using default scales.
/// Example: const V = Quantities.Velocity.Base(f32);
pub fn Of(comptime T: type) type {
return Scalar(T, dims, Scales.init(.{}));
return Scalar(T, d, .{});
}
/// Creates a Scalar of this dimension using custom scales.
/// Example: const Kmh = Quantities.Velocity.Scaled(f32, Scales.init(.{ .L = .k, .T = .hour }));
pub fn Scaled(comptime T: type, comptime s: Scales) type {
return Scalar(T, dims, s);
pub fn Scaled(comptime T: type, comptime s: Scales.ArgOpts) type {
return Scalar(T, d, s);
}
};
}
@ -95,6 +105,31 @@ pub const Frequency = BaseScalar(.{ .T = -1 });
pub const Viscosity = BaseScalar(.{ .M = 1, .L = -1, .T = -1 });
pub const SurfaceTension = BaseScalar(.{ .M = 1, .T = -2 }); // Corrected from MT-2a
// ==========================================
// Physical Constants
// ==========================================
pub const Constants = struct {
/// Speed of light in vacuum
pub const c = Speed.Constant(299792458.0, Scales.init(.{}));
/// Standard gravity
pub const g = Acceleration.Constant(9.80665, Scales.init(.{}));
/// Newton's Gravitational Constant: L³ M¹ T²
pub const GravitationalConstant = PhysicalConstant(
.{ .L = 3, .M = -1, .T = -2 },
6.67430e-11,
Scales.init(.{}),
);
/// Planck Constant: M L² T¹
pub const Planck = PhysicalConstant(
.{ .M = 1, .L = 2, .T = -1 },
6.62607015e-34,
Scales.init(.{}),
);
};
test "BaseQuantities - Core dimensions instantiation" {
// Basic types via generic wrappers
const M = Meter.Of(f32);
@ -104,7 +139,7 @@ test "BaseQuantities - Core dimensions instantiation" {
try std.testing.expectEqual(0, M.dims.get(.T));
// Test specific scale variants
const Kmh = Speed.Scaled(f32, Scales.init(.{ .L = .k, .T = .hour }));
const Kmh = Speed.Scaled(f32, .{ .L = .k, .T = .hour });
const speed = Kmh{ .value = 120.0 };
try std.testing.expectEqual(120.0, speed.value);
try std.testing.expectEqual(.k, @TypeOf(speed).scales.get(.L));
@ -128,7 +163,7 @@ test "BaseQuantities - Kinematics equations" {
test "BaseQuantities - Dynamics (Force and Work)" {
// 10 kg
const m = Gramm.Scaled(f32, Scales.init(.{ .M = .k })){ .value = 10.0 };
const m = Gramm.Scaled(f32, .{ .M = .k }){ .value = 10.0 };
// 9.8 m/s^2
const a = Acceleration.Of(f32){ .value = 9.8 };

View File

@ -1,5 +1,15 @@
const std = @import("std");
pub const ArgOpts = struct {
L: comptime_int = 0,
M: comptime_int = 0,
T: comptime_int = 0,
I: comptime_int = 0,
Tp: comptime_int = 0,
N: comptime_int = 0,
J: comptime_int = 0,
};
pub const Dimension = enum {
/// Length
L,
@ -37,9 +47,9 @@ const Self = @This();
data: std.EnumArray(Dimension, comptime_int),
/// Create a `Dimensions` from an anonymous struct literal, e.g. `.{ .L = 1, .T = -1 }`.
/// Create a `Dimensions` from a struct literal, e.g. `.{ .L = 1, .T = -1 }`.
/// Unspecified dimensions default to 0.
pub fn init(comptime init_val: anytype) Self {
pub fn init(comptime init_val: ArgOpts) Self {
var s = Self{ .data = std.EnumArray(Dimension, comptime_int).initFill(0) };
inline for (std.meta.fields(@TypeOf(init_val))) |f|
s.data.set(@field(Dimension, f.name), @field(init_val, f.name));

View File

@ -7,12 +7,17 @@ const UnitScale = Scales.UnitScale;
const Dimensions = @import("Dimensions.zig");
const Dimension = Dimensions.Dimension;
// TODO: Be able to use comptime float and int and T for mulBy ect
// TODO:
// - Be able to use comptime float and int and T for mulBy ect
// Which endup being Dimension less
/// A dimensioned scalar value. `T` is the numeric type, `d` the dimension exponents, `s` the SI scales.
/// All dimension and unit tracking is resolved at comptime zero runtime overhead.
pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type {
pub fn Scalar(comptime T: type, comptime d_opt: Dimensions.ArgOpts, comptime s_opt: Scales.ArgOpts) type {
return Scalar_(T, Dimensions.init(d_opt), Scales.init(s_opt));
}
pub fn Scalar_(comptime T: type, comptime d: Dimensions, comptime s: Scales) type {
@setEvalBranchQuota(10_000_000);
return struct {
value: T,
@ -33,7 +38,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
/// Add two quantities. Dimensions must match compile error otherwise.
/// Scales are auto-resolved to the finer of the two.
pub inline fn add(self: Self, rhs: anytype) Scalar(
pub inline fn add(self: Self, rhs: anytype) Scalar_(
T,
dims,
hlp.finerScales(Self, @TypeOf(rhs)),
@ -43,7 +48,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
if (comptime @TypeOf(rhs) == Self)
return .{ .value = self.value + rhs.value };
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const TargetType = Scalar_(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
@ -52,7 +57,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
/// Subtract two quantities. Dimensions must match compile error otherwise.
/// Scales are auto-resolved to the finer of the two.
pub inline fn sub(self: Self, rhs: anytype) Scalar(
pub inline fn sub(self: Self, rhs: anytype) Scalar_(
T,
dims,
hlp.finerScales(Self, @TypeOf(rhs)),
@ -62,7 +67,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
if (comptime @TypeOf(rhs) == Self)
return .{ .value = self.value - rhs.value };
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const TargetType = Scalar_(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
@ -70,14 +75,14 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
}
/// Multiply two quantities. Dimension exponents are summed: `L¹ * T¹ L¹T¹`.
pub inline fn mulBy(self: Self, rhs: anytype) Scalar(
pub inline fn mulBy(self: Self, rhs: anytype) Scalar_(
T,
dims.add(@TypeOf(rhs).dims),
hlp.finerScales(Self, @TypeOf(rhs)),
) {
const RhsType = @TypeOf(rhs);
const SelfNorm = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const RhsNorm = Scalar(T, RhsType.dims, hlp.finerScales(Self, @TypeOf(rhs)));
const SelfNorm = Scalar_(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const RhsNorm = Scalar_(T, RhsType.dims, hlp.finerScales(Self, @TypeOf(rhs)));
if (comptime Self == SelfNorm and RhsType == RhsNorm)
return .{ .value = self.value * rhs.value };
@ -88,14 +93,14 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
/// Divide two quantities. Dimension exponents are subtracted: `L¹ / T¹ L¹T¹`.
/// Integer types use truncating division.
pub inline fn divBy(self: Self, rhs: anytype) Scalar(
pub inline fn divBy(self: Self, rhs: anytype) Scalar_(
T,
dims.sub(@TypeOf(rhs).dims),
hlp.finerScales(Self, @TypeOf(rhs)),
) {
const RhsType = @TypeOf(rhs);
const SelfNorm = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const RhsNorm = Scalar(T, RhsType.dims, hlp.finerScales(Self, @TypeOf(rhs)));
const SelfNorm = Scalar_(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const RhsNorm = Scalar_(T, RhsType.dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime Self == SelfNorm) self.value else self.to(SelfNorm).value;
const rhs_val = if (comptime RhsType == RhsNorm) rhs.value else rhs.to(RhsNorm).value;
if (comptime @typeInfo(T) == .int) {
@ -116,7 +121,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
/// Raises the quantity to a compile-time integer exponent.
/// Dimension exponents are multiplied by the exponent: `(L²)³ L`.
pub inline fn pow(self: Self, comptime exp: comptime_int) Scalar(
pub inline fn pow(self: Self, comptime exp: comptime_int) Scalar_(
T,
dims.scale(exp),
s,
@ -127,7 +132,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
return .{ .value = std.math.pow(T, self.value, @as(T, @floatFromInt(exp))) };
}
pub inline fn sqrt(self: Self) Scalar(
pub inline fn sqrt(self: Self) Scalar_(
T,
dims.div(2),
s,
@ -196,7 +201,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
if (comptime @TypeOf(rhs) == Self)
return self.value == rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const TargetType = Scalar_(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
@ -211,7 +216,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
if (comptime @TypeOf(rhs) == Self)
return self.value != rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const TargetType = Scalar_(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
@ -226,7 +231,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
if (comptime @TypeOf(rhs) == Self)
return self.value > rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const TargetType = Scalar_(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
@ -240,7 +245,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
if (comptime @TypeOf(rhs) == Self)
return self.value >= rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const TargetType = Scalar_(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
@ -255,7 +260,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
if (comptime @TypeOf(rhs) == Self)
return self.value < rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const TargetType = Scalar_(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
@ -269,7 +274,7 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
if (comptime @TypeOf(rhs) == Self)
return self.value <= rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const TargetType = Scalar_(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
@ -329,8 +334,8 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
}
test "Generate quantity" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = -3 }));
const Second = Scalar(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{ .T = .n }));
const Meter = Scalar(i128, .{ .L = 1 }, .{ .L = @enumFromInt(-3) });
const Second = Scalar(f32, .{ .T = 1 }, .{ .T = .n });
const distance = Meter{ .value = 10 };
const time = Second{ .value = 2 };
@ -340,8 +345,8 @@ test "Generate quantity" {
}
test "Comparisons (eq, ne, gt, gte, lt, lte)" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const KiloMeter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const KiloMeter = Scalar(i128, .{ .L = 1 }, .{ .L = .k });
const m1000 = Meter{ .value = 1000 };
const km1 = KiloMeter{ .value = 1 };
@ -366,7 +371,7 @@ test "Comparisons (eq, ne, gt, gte, lt, lte)" {
}
test "Add" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const distance = Meter{ .value = 10 };
const distance2 = Meter{ .value = 20 };
@ -375,7 +380,7 @@ test "Add" {
try std.testing.expectEqual(30, added.value);
try std.testing.expectEqual(1, @TypeOf(added).dims.get(.L));
const KiloMeter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const KiloMeter = Scalar(i128, .{ .L = 1 }, .{ .L = .k });
const distance3 = KiloMeter{ .value = 2 };
const added2 = distance.add(distance3);
try std.testing.expectEqual(2010, added2.value);
@ -385,7 +390,7 @@ test "Add" {
try std.testing.expectEqual(2, added3.value);
try std.testing.expectEqual(1, @TypeOf(added3).dims.get(.L));
const KiloMeter_f = Scalar(f64, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const KiloMeter_f = Scalar(f64, .{ .L = 1 }, .{ .L = .k });
const distance4 = KiloMeter_f{ .value = 2 };
const added4 = distance4.add(distance).to(KiloMeter_f);
try std.testing.expectApproxEqAbs(2.01, added4.value, 0.000001);
@ -393,8 +398,8 @@ test "Add" {
}
test "Sub" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const KiloMeter_f = Scalar(f64, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const KiloMeter_f = Scalar(f64, .{ .L = 1 }, .{ .L = .k });
const a = Meter{ .value = 500 };
const b = Meter{ .value = 200 };
@ -410,8 +415,8 @@ test "Sub" {
}
test "MulBy" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Second = Scalar(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const Second = Scalar(f32, .{ .T = 1 }, .{});
const d = Meter{ .value = 3.0 };
const t = Second{ .value = 4.0 };
@ -429,8 +434,8 @@ test "MulBy" {
}
test "MulBy with scale" {
const KiloMeter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const KiloGram = Scalar(f32, Dimensions.init(.{ .M = 1 }), Scales.init(.{ .M = .k }));
const KiloMeter = Scalar(f32, .{ .L = 1 }, .{ .L = .k });
const KiloGram = Scalar(f32, .{ .M = 1 }, .{ .M = .k });
const dist = KiloMeter{ .value = 2.0 };
const mass = KiloGram{ .value = 3.0 };
@ -440,10 +445,10 @@ test "MulBy with scale" {
}
test "MulBy with type change" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const Second = Scalar(f64, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
const KmSec = Scalar(i64, Dimensions.init(.{ .L = 1, .T = 1 }), Scales.init(.{ .L = .k }));
const KmSec_f = Scalar(f32, Dimensions.init(.{ .L = 1, .T = 1 }), Scales.init(.{ .L = .k }));
const Meter = Scalar(i128, .{ .L = 1 }, .{ .L = .k });
const Second = Scalar(f64, .{ .T = 1 }, .{});
const KmSec = Scalar(i64, .{ .L = 1, .T = 1 }, .{ .L = .k });
const KmSec_f = Scalar(f32, .{ .L = 1, .T = 1 }, .{ .L = .k });
const d = Meter{ .value = 3.0 };
const t = Second{ .value = 4.0 };
@ -457,8 +462,8 @@ test "MulBy with type change" {
}
test "MulBy small" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .n }));
const Second = Scalar(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
const Meter = Scalar(i128, .{ .L = 1 }, .{ .L = .n });
const Second = Scalar(f32, .{ .T = 1 }, .{});
const d = Meter{ .value = 3.0 };
const t = Second{ .value = 4.0 };
@ -470,8 +475,8 @@ test "MulBy small" {
}
test "MulBy dimensionless" {
const DimLess = Scalar(i128, Dimensions.init(.{}), Scales.init(.{}));
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const DimLess = Scalar(i128, .{}, .{});
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const d = Meter{ .value = 7 };
const scaled = d.mulBy(DimLess{ .value = 3 });
@ -480,7 +485,7 @@ test "MulBy dimensionless" {
}
test "Sqrt" {
const MeterSquare = Scalar(i128, Dimensions.init(.{ .L = 2 }), Scales.init(.{}));
const MeterSquare = Scalar(i128, .{ .L = 2 }, .{});
var d = MeterSquare{ .value = 9 };
var scaled = d.sqrt();
@ -492,7 +497,7 @@ test "Sqrt" {
try std.testing.expectEqual(0, scaled.value);
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
const MeterSquare_f = Scalar(f64, Dimensions.init(.{ .L = 2 }), Scales.init(.{}));
const MeterSquare_f = Scalar(f64, .{ .L = 2 }, .{});
const d2 = MeterSquare_f{ .value = 20 };
const scaled2 = d2.sqrt();
try std.testing.expectApproxEqAbs(4.472135955, scaled2.value, 1e-4);
@ -500,8 +505,8 @@ test "Sqrt" {
}
test "Chained: velocity and acceleration" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Second = Scalar(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const Second = Scalar(f32, .{ .T = 1 }, .{});
const dist = Meter{ .value = 100.0 };
const t1 = Second{ .value = 5.0 };
@ -518,8 +523,8 @@ test "Chained: velocity and acceleration" {
}
test "DivBy integer exact" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Second = Scalar(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const Second = Scalar(f32, .{ .T = 1 }, .{});
const dist = Meter{ .value = 120 };
const time = Second{ .value = 4 };
@ -531,8 +536,8 @@ test "DivBy integer exact" {
}
test "Finer scales skip dim 0" {
const Dimless = Scalar(i128, Dimensions.init(.{}), Scales.init(.{}));
const KiloMetre = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const Dimless = Scalar(i128, .{}, .{});
const KiloMetre = Scalar(i128, .{ .L = 1 }, .{ .L = .k });
const r = Dimless{ .value = 30 };
const time = KiloMetre{ .value = 4 };
@ -543,9 +548,9 @@ test "Finer scales skip dim 0" {
}
test "Conversion chain: km -> m -> cm" {
const KiloMeter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const CentiMeter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .c }));
const KiloMeter = Scalar(i128, .{ .L = 1 }, .{ .L = .k });
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const CentiMeter = Scalar(i128, .{ .L = 1 }, .{ .L = .c });
const km = KiloMeter{ .value = 15 };
const m = km.to(Meter);
@ -556,9 +561,9 @@ test "Conversion chain: km -> m -> cm" {
}
test "Conversion: hours -> minutes -> seconds" {
const Hour = Scalar(i128, Dimensions.init(.{ .T = 1 }), Scales.init(.{ .T = .hour }));
const Minute = Scalar(i128, Dimensions.init(.{ .T = 1 }), Scales.init(.{ .T = .min }));
const Second = Scalar(i128, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
const Hour = Scalar(i128, .{ .T = 1 }, .{ .T = .hour });
const Minute = Scalar(i128, .{ .T = 1 }, .{ .T = .min });
const Second = Scalar(i128, .{ .T = 1 }, .{});
const h = Hour{ .value = 1.0 };
const min = h.to(Minute);
@ -569,7 +574,7 @@ test "Conversion: hours -> minutes -> seconds" {
}
test "Negative values" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const a = Meter{ .value = 5 };
const b = Meter{ .value = 20 };
@ -578,17 +583,9 @@ test "Negative values" {
}
test "Format Scalar" {
const MeterPerSecondSq = Scalar(
f32,
Dimensions.init(.{ .L = 1, .T = -2 }),
Scales.init(.{ .T = .n }),
);
const KgMeterPerSecond = Scalar(
f32,
Dimensions.init(.{ .M = 1, .L = 1, .T = -1 }),
Scales.init(.{ .M = .k }),
);
const Meter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const MeterPerSecondSq = Scalar(f32, .{ .L = 1, .T = -2 }, .{ .T = .n });
const KgMeterPerSecond = Scalar(f32, .{ .M = 1, .L = 1, .T = -1 }, .{ .M = .k });
const Meter = Scalar(f32, .{ .L = 1 }, .{});
const m = Meter{ .value = 1.23456 };
const accel = MeterPerSecondSq{ .value = 9.81 };
@ -609,20 +606,20 @@ test "Format Scalar" {
}
test "Abs" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const m1 = Meter{ .value = -50 };
const m2 = m1.abs();
try std.testing.expectEqual(50, m2.value);
try std.testing.expectEqual(1, @TypeOf(m2).dims.get(.L));
const m_float = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const m_float = Scalar(f32, .{ .L = 1 }, .{});
const m3 = m_float{ .value = -42.5 };
try std.testing.expectEqual(42.5, m3.abs().value);
}
test "Pow" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Meter = Scalar(i128, .{ .L = 1 }, .{});
const d = Meter{ .value = 4 };
const area = d.pow(2);
@ -634,7 +631,7 @@ test "Pow" {
try std.testing.expectEqual(3, @TypeOf(volume).dims.get(.L));
// Float test
const MeterF = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const MeterF = Scalar(f32, .{ .L = 1 }, .{});
const d_f = MeterF{ .value = 2.0 };
const area_f = d_f.pow(3);
try std.testing.expectEqual(8.0, area_f.value);

View File

@ -3,6 +3,17 @@ const hlp = @import("helper.zig");
const Dimensions = @import("Dimensions.zig");
const Dimension = @import("Dimensions.zig").Dimension;
/// Use to initiate Scalar and Scales type
pub const ArgOpts = struct {
L: UnitScale = .none,
M: UnitScale = .none,
T: UnitScale = .none,
I: UnitScale = .none,
Tp: UnitScale = .none,
N: UnitScale = .none,
J: UnitScale = .none,
};
/// SI prefix (picopeta) plus time-unit aliases (min, hour, year).
/// The integer value encodes the exponent for SI prefixes (e.g. `k = 3` 10³),
/// and the literal factor for time units (e.g. `hour = 3600`).
@ -62,9 +73,9 @@ const Scales = @This();
data: std.EnumArray(Dimension, UnitScale),
/// Create a `Scales` from an anonymous struct literal, e.g. `.{ .L = .k, .T = .hour }`.
/// Create a `Scales` from a struct literal, e.g. `.{ .L = .k, .T = .hour }`.
/// Unspecified dimensions default to `.none` (factor 1).
pub fn init(comptime init_val: anytype) Scales {
pub fn init(comptime init_val: ArgOpts) Scales {
comptime var s = Scales{ .data = std.EnumArray(Dimension, UnitScale).initFill(.none) };
inline for (std.meta.fields(@TypeOf(init_val))) |f| {
if (comptime hlp.isInt(@TypeOf(@field(init_val, f.name))))

View File

@ -2,6 +2,7 @@ const std = @import("std");
const hlp = @import("helper.zig");
const Scalar = @import("Scalar.zig").Scalar;
const Scalar_ = @import("Scalar.zig").Scalar_;
const Scales = @import("Scales.zig");
const UnitScale = Scales.UnitScale;
const Dimensions = @import("Dimensions.zig");
@ -32,13 +33,13 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
}
/// 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(
pub inline fn add(self: Self, rhs: anytype) Vector(len, Scalar_(
T,
dims,
hlp.finerScales(Self, @TypeOf(rhs)),
)) {
const Tr = @TypeOf(rhs);
var res: Vector(len, Scalar(T, d, hlp.finerScales(Self, @TypeOf(rhs)))) = undefined;
var res: Vector(len, Scalar_(T, d, hlp.finerScales(Self, @TypeOf(rhs)))) = 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;
@ -46,13 +47,13 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
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(
pub inline fn sub(self: Self, rhs: anytype) Vector(len, Scalar_(
T,
dims,
hlp.finerScales(Self, @TypeOf(rhs)),
)) {
const Tr = @TypeOf(rhs);
var res: Vector(len, Scalar(T, d, hlp.finerScales(Self, @TypeOf(rhs)))) = undefined;
var res: Vector(len, Scalar_(T, d, hlp.finerScales(Self, @TypeOf(rhs)))) = 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;
@ -64,13 +65,13 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
pub inline fn divBy(
self: Self,
rhs: anytype,
) Vector(len, Scalar(
) Vector(len, Scalar_(
T,
dims.sub(@TypeOf(rhs).dims),
hlp.finerScales(Self, @TypeOf(rhs)),
)) {
const Tr = @TypeOf(rhs);
var res: Vector(len, Scalar(T, d.sub(Tr.dims), hlp.finerScales(Self, @TypeOf(rhs)))) = undefined;
var res: Vector(len, Scalar_(T, d.sub(Tr.dims), hlp.finerScales(Self, @TypeOf(rhs)))) = undefined;
inline for (self.data, 0..) |v, i| {
const q = (Q{ .value = v }).divBy(Tr.ScalarType{ .value = rhs.data[i] });
res.data[i] = q.value;
@ -82,13 +83,13 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
pub inline fn mulBy(
self: Self,
rhs: anytype,
) Vector(len, Scalar(
) Vector(len, Scalar_(
T,
dims.add(@TypeOf(rhs).dims),
hlp.finerScales(Self, @TypeOf(rhs)),
)) {
const Tr = @TypeOf(rhs);
var res: Vector(len, Scalar(T, d.add(Tr.dims), hlp.finerScales(Self, @TypeOf(rhs)))) = undefined;
var res: Vector(len, Scalar_(T, d.add(Tr.dims), hlp.finerScales(Self, @TypeOf(rhs)))) = undefined;
inline for (self.data, 0..) |v, i| {
const q = (Q{ .value = v }).mulBy(Tr.ScalarType{ .value = rhs.data[i] });
res.data[i] = q.value;
@ -100,12 +101,12 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
pub inline fn divByScalar(
self: Self,
scalar: anytype,
) Vector(len, Scalar(
) Vector(len, Scalar_(
T,
dims.sub(@TypeOf(scalar).dims),
hlp.finerScales(Self, @TypeOf(scalar)),
)) {
var res: Vector(len, Scalar(T, d.sub(@TypeOf(scalar).dims), hlp.finerScales(Self, @TypeOf(scalar)))) = undefined;
var res: Vector(len, Scalar_(T, d.sub(@TypeOf(scalar).dims), hlp.finerScales(Self, @TypeOf(scalar)))) = undefined;
inline for (self.data, 0..) |v, i| {
const q = Q{ .value = v };
res.data[i] = q.divBy(scalar).value;
@ -117,12 +118,12 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
pub inline fn mulByScalar(
self: Self,
scalar: anytype,
) Vector(len, Scalar(
) Vector(len, Scalar_(
T,
dims.add(@TypeOf(scalar).dims),
hlp.finerScales(Self, @TypeOf(scalar)),
)) {
var res: Vector(len, Scalar(T, d.add(@TypeOf(scalar).dims), hlp.finerScales(Self, @TypeOf(scalar)))) = undefined;
var res: Vector(len, Scalar_(T, d.add(@TypeOf(scalar).dims), hlp.finerScales(Self, @TypeOf(scalar)))) = undefined;
inline for (self.data, 0..) |v, i| {
const q = Q{ .value = v };
res.data[i] = q.mulBy(scalar).value;
@ -132,7 +133,7 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
/// 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(
pub inline fn dot(self: Self, rhs: anytype) Scalar_(
T,
dims.add(@TypeOf(rhs).dims),
hlp.finerScales(Self, @TypeOf(rhs)),
@ -150,7 +151,7 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
/// 3D Cross product. Dimensions are summed.
/// Only valid for vectors of length 3.
pub inline fn cross(self: Self, rhs: anytype) Vector(3, Scalar(
pub inline fn cross(self: Self, rhs: anytype) Vector(3, Scalar_(
T,
dims.add(@TypeOf(rhs).dims),
hlp.finerScales(Self, @TypeOf(rhs)),
@ -159,7 +160,7 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
@compileError("Cross product is only defined for Vector(3, ...)");
const Tr = @TypeOf(rhs);
const ResScalar = Scalar(T, d.add(Tr.dims), hlp.finerScales(Self, Tr));
const ResScalar = Scalar_(T, d.add(Tr.dims), hlp.finerScales(Self, Tr));
const ResVec = Vector(3, ResScalar);
// Calculation: [y1*z2 - z1*y2, z1*x2 - x1*z2, x1*y2 - y1*x2]
@ -202,7 +203,7 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
/// Multiplies all components of the vector together.
/// Resulting dimensions are (Original Dims * len).
pub inline fn product(self: Self) Scalar(
pub inline fn product(self: Self) Scalar_(
T,
dims.scale(len),
scales,
@ -215,12 +216,12 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
/// 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(
pub inline fn pow(self: Self, comptime exp: comptime_int) Vector(len, Scalar_(
T,
dims.scale(exp),
scales,
)) {
const ResScalar = Scalar(T, dims.scale(exp), s);
const ResScalar = Scalar_(T, dims.scale(exp), s);
var res: Vector(len, ResScalar) = undefined;
inline for (self.data, 0..) |v, i| {
const q = Q{ .value = v };
@ -436,16 +437,8 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
}
test "Format VectorX" {
const MeterPerSecondSq = Scalar(
f32,
Dimensions.init(.{ .L = 1, .T = -2 }),
Scales.init(.{ .T = .n }),
);
const KgMeterPerSecond = Scalar(
f32,
Dimensions.init(.{ .M = 1, .L = 1, .T = -1 }),
Scales.init(.{ .M = .k }),
);
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 } };
@ -459,7 +452,7 @@ test "Format VectorX" {
}
test "VecX Init and Basic Arithmetic" {
const Meter = Scalar(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Meter = Scalar(i32, .{ .L = 1 }, .{});
const Vec3M = Meter.Vec3;
// Test zero, one, initDefault
@ -500,8 +493,8 @@ test "VecX Init and Basic Arithmetic" {
}
test "VecX Kinematics (Scalar Mul/Div)" {
const Meter = Scalar(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Second = Scalar(i32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
const Meter = Scalar(i32, .{ .L = 1 }, .{});
const Second = Scalar(i32, .{ .T = 1 }, .{});
const Vec3M = Meter.Vec3;
const pos = Vec3M{ .data = .{ 100, 200, 300 } };
@ -525,7 +518,7 @@ test "VecX Kinematics (Scalar Mul/Div)" {
}
test "VecX Element-wise Math and Scaling" {
const Meter = Scalar(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Meter = Scalar(i32, .{ .L = 1 }, .{});
const Vec3M = Meter.Vec3;
const v1 = Vec3M{ .data = .{ 10, 20, 30 } };
@ -540,8 +533,8 @@ test "VecX Element-wise Math and Scaling" {
}
test "VecX Conversions" {
const KiloMeter = Scalar(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const Meter = Scalar(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
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);
@ -556,8 +549,8 @@ test "VecX Conversions" {
}
test "VecX Length" {
const MeterInt = Scalar(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const MeterFloat = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const MeterInt = Scalar(i32, .{ .L = 1 }, .{});
const MeterFloat = Scalar(f32, .{ .L = 1 }, .{});
// Integer length
// 3-4-5 triangle on XY plane
@ -572,8 +565,8 @@ test "VecX Length" {
}
test "Vector Comparisons" {
const Meter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const KiloMeter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
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 } };
@ -601,8 +594,8 @@ test "Vector Comparisons" {
}
test "Vector vs Scalar Comparisons" {
const Meter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const KiloMeter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
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)
@ -621,8 +614,8 @@ test "Vector vs Scalar Comparisons" {
}
test "Vector Dot and Cross Products" {
const Meter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Newton = Scalar(f32, Dimensions.init(.{ .M = 1, .L = 1, .T = -2 }), Scales.init(.{}));
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 } };
@ -645,7 +638,7 @@ test "Vector Dot and Cross Products" {
}
test "Vector Abs, Pow, Sqrt and Product" {
const Meter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const Meter = Scalar(f32, .{ .L = 1 }, .{});
const v1 = Meter.Vec3{ .data = .{ -2.0, 3.0, -4.0 } };