diff --git a/src/Base.zig b/src/Base.zig index 9928819..0f9714b 100644 --- a/src/Base.zig +++ b/src/Base.zig @@ -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 }; diff --git a/src/Dimensions.zig b/src/Dimensions.zig index 92e3531..3953fe0 100644 --- a/src/Dimensions.zig +++ b/src/Dimensions.zig @@ -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)); diff --git a/src/Scalar.zig b/src/Scalar.zig index 55564fb..60b6965 100644 --- a/src/Scalar.zig +++ b/src/Scalar.zig @@ -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 -// Which endup being Dimension less +// 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); diff --git a/src/Scales.zig b/src/Scales.zig index e80ae79..f300e28 100644 --- a/src/Scales.zig +++ b/src/Scales.zig @@ -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 (pico…peta) 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)))) diff --git a/src/Vector.zig b/src/Vector.zig index cce42b8..54657b2 100644 --- a/src/Vector.zig +++ b/src/Vector.zig @@ -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 } };