Replace Scalar and Vector to a single Quantity that use @Vector #1
29
.gitea/workflows/deploy.yml
Normal file
29
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: Deploy MkDocs to Garage
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main # Adjust to your branch name
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build MkDocs Material
|
||||||
|
# We use the official image to build the site into the 'site' folder
|
||||||
|
run: |
|
||||||
|
docker run --rm -v "${{ github.workspace }}:/docs" \
|
||||||
|
squidfunk/mkdocs-material build
|
||||||
|
|
||||||
|
- name: Sync to Garage S3
|
||||||
|
uses: https://github.com/jakejarvis/s3-sync-action@master
|
||||||
|
with:
|
||||||
|
args: --endpoint-url https://s3.garage.bouvais.lu --acl public-read --delete
|
||||||
|
env:
|
||||||
|
AWS_S3_BUCKET: 'zig-dimal.bouvais.lu'
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.GARAGE_ACCESS_KEY }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.GARAGE_SECRET_KEY }}
|
||||||
|
AWS_REGION: 'garage'
|
||||||
|
SOURCE_DIR: 'site' # MkDocs defaults to 'site' folder for output
|
||||||
26
docs/index.md
Normal file
26
docs/index.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Welcome to My Project
|
||||||
|
|
||||||
|
This is a static site hosted via **Gitea Actions** and **Garage S3 Storage**.
|
||||||
|
|
||||||
|
!!! info "Status"
|
||||||
|
The deployment pipeline is currently **Active**.
|
||||||
|
Updates to the `main` branch are pushed automatically.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
To replicate this setup, you need:
|
||||||
|
1. **Traefik** as the reverse proxy.
|
||||||
|
2. **Garage** for S3-compatible web hosting.
|
||||||
|
3. **Gitea** for version control and CI.
|
||||||
|
|
||||||
|
### Deployment Details
|
||||||
|
| Component | Technology |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **Engine** | MkDocs Material |
|
||||||
|
| **Hosting** | Garage S3 |
|
||||||
|
| **Routing** | Traefik |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
If you have questions, reach out via the Gitea instance.
|
||||||
48
mkdocs.yml
Normal file
48
mkdocs.yml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
site_name: Bouvais Docs
|
||||||
|
site_url: https://zig-dimal.bouvais.lu
|
||||||
|
site_description: A minimal technical documentation site.
|
||||||
|
site_author: Adrien Bouvais
|
||||||
|
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
language: en
|
||||||
|
# Color palette with auto light/dark mode
|
||||||
|
palette:
|
||||||
|
- media: "(prefers-color-scheme: light)"
|
||||||
|
scheme: default
|
||||||
|
primary: indigo
|
||||||
|
accent: indigo
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-7
|
||||||
|
name: Switch to dark mode
|
||||||
|
- media: "(prefers-color-scheme: dark)"
|
||||||
|
scheme: slate
|
||||||
|
primary: indigo
|
||||||
|
accent: indigo
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-4
|
||||||
|
name: Switch to light mode
|
||||||
|
|
||||||
|
features:
|
||||||
|
- navigation.sections
|
||||||
|
- navigation.top
|
||||||
|
- content.code.copy
|
||||||
|
- content.code.annotate
|
||||||
|
|
||||||
|
# Minimal plugins
|
||||||
|
plugins:
|
||||||
|
- search
|
||||||
|
|
||||||
|
# Your single page
|
||||||
|
nav:
|
||||||
|
- Home: index.md
|
||||||
|
|
||||||
|
# Extensions to make your markdown look better
|
||||||
|
markdown_extensions:
|
||||||
|
- admonition
|
||||||
|
- pymdownx.details
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.highlight:
|
||||||
|
anchor_linenums: true
|
||||||
|
- pymdownx.inlinehilite
|
||||||
|
- attr_list
|
||||||
46
src/Base.zig
46
src/Base.zig
@ -3,7 +3,7 @@ const std = @import("std");
|
|||||||
// Adjust these imports to match your actual file names
|
// Adjust these imports to match your actual file names
|
||||||
const Dimensions = @import("Dimensions.zig");
|
const Dimensions = @import("Dimensions.zig");
|
||||||
const Scales = @import("Scales.zig");
|
const Scales = @import("Scales.zig");
|
||||||
const Scalar = @import("Scalar.zig").Scalar;
|
const Scalar = @import("Quantity.zig").Scalar;
|
||||||
|
|
||||||
fn PhysicalConstant(comptime d: Dimensions.ArgOpts, comptime val: f64, comptime s: Scales.ArgOpts) type {
|
fn PhysicalConstant(comptime d: Dimensions.ArgOpts, comptime val: f64, comptime s: Scales.ArgOpts) type {
|
||||||
return struct {
|
return struct {
|
||||||
@ -12,7 +12,7 @@ fn PhysicalConstant(comptime d: Dimensions.ArgOpts, comptime val: f64, comptime
|
|||||||
|
|
||||||
/// Instantiates the constant into a specific numeric type.
|
/// Instantiates the constant into a specific numeric type.
|
||||||
pub fn Of(comptime T: type) Scalar(T, d, s) {
|
pub fn Of(comptime T: type) Scalar(T, d, s) {
|
||||||
return .{ .value = @as(T, @floatCast(val)) };
|
return .{ .data = @splat(@as(T, @floatCast(val))) };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -157,78 +157,78 @@ pub const SurfaceTension = BaseScalar(.{ .M = 1, .T = -2 }); // Corrected from M
|
|||||||
test "BaseQuantities - Core dimensions instantiation" {
|
test "BaseQuantities - Core dimensions instantiation" {
|
||||||
// Basic types via generic wrappers
|
// Basic types via generic wrappers
|
||||||
const M = Meter.Of(f32);
|
const M = Meter.Of(f32);
|
||||||
const distance = M{ .value = 100.0 };
|
const distance = M.splat(100);
|
||||||
try std.testing.expectEqual(100.0, distance.value);
|
try std.testing.expectEqual(100.0, distance.value());
|
||||||
try std.testing.expectEqual(1, M.dims.get(.L));
|
try std.testing.expectEqual(1, M.dims.get(.L));
|
||||||
try std.testing.expectEqual(0, M.dims.get(.T));
|
try std.testing.expectEqual(0, M.dims.get(.T));
|
||||||
|
|
||||||
// Test specific scale variants
|
// Test specific scale variants
|
||||||
const Kmh = Speed.Scaled(f32, .{ .L = .k, .T = .hour });
|
const Kmh = Speed.Scaled(f32, .{ .L = .k, .T = .hour });
|
||||||
const speed = Kmh{ .value = 120.0 };
|
const speed = Kmh.splat(120);
|
||||||
try std.testing.expectEqual(120.0, speed.value);
|
try std.testing.expectEqual(120.0, speed.value());
|
||||||
try std.testing.expectEqual(.k, @TypeOf(speed).scales.get(.L));
|
try std.testing.expectEqual(.k, @TypeOf(speed).scales.get(.L));
|
||||||
try std.testing.expectEqual(.hour, @TypeOf(speed).scales.get(.T));
|
try std.testing.expectEqual(.hour, @TypeOf(speed).scales.get(.T));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "BaseQuantities - Kinematics equations" {
|
test "BaseQuantities - Kinematics equations" {
|
||||||
const d = Meter.Of(f32){ .value = 50.0 };
|
const d = Meter.Of(f32).splat(50.0);
|
||||||
const t = Second.Of(f32){ .value = 2.0 };
|
const t = Second.Of(f32).splat(2.0);
|
||||||
|
|
||||||
// Velocity = Distance / Time
|
// Velocity = Distance / Time
|
||||||
const v = d.div(t);
|
const v = d.div(t);
|
||||||
try std.testing.expectEqual(25.0, v.value);
|
try std.testing.expectEqual(25.0, v.value());
|
||||||
try std.testing.expect(Speed.dims.eql(@TypeOf(v).dims));
|
try std.testing.expect(Speed.dims.eql(@TypeOf(v).dims));
|
||||||
|
|
||||||
// Acceleration = Velocity / Time
|
// Acceleration = Velocity / Time
|
||||||
const a = v.div(t);
|
const a = v.div(t);
|
||||||
try std.testing.expectEqual(12.5, a.value);
|
try std.testing.expectEqual(12.5, a.value());
|
||||||
try std.testing.expect(Acceleration.dims.eql(@TypeOf(a).dims));
|
try std.testing.expect(Acceleration.dims.eql(@TypeOf(a).dims));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "BaseQuantities - Dynamics (Force and Work)" {
|
test "BaseQuantities - Dynamics (Force and Work)" {
|
||||||
// 10 kg
|
// 10 kg
|
||||||
const m = Gramm.Scaled(f32, .{ .M = .k }){ .value = 10.0 };
|
const m = Gramm.Scaled(f32, .{ .M = .k }).splat(10.0);
|
||||||
// 9.8 m/s^2
|
// 9.8 m/s^2
|
||||||
const a = Acceleration.Of(f32){ .value = 9.8 };
|
const a = Acceleration.Of(f32).splat(9.8);
|
||||||
|
|
||||||
// Force = mass * acceleration
|
// Force = mass * acceleration
|
||||||
const f = m.mul(a);
|
const f = m.mul(a);
|
||||||
try std.testing.expectEqual(98, f.value);
|
try std.testing.expectEqual(98, f.value());
|
||||||
try std.testing.expect(Force.dims.eql(@TypeOf(f).dims));
|
try std.testing.expect(Force.dims.eql(@TypeOf(f).dims));
|
||||||
|
|
||||||
// Energy (Work) = Force * distance
|
// Energy (Work) = Force * distance
|
||||||
const distance = Meter.Of(f32){ .value = 5.0 };
|
const distance = Meter.Of(f32).splat(5.0);
|
||||||
const energy = f.mul(distance);
|
const energy = f.mul(distance);
|
||||||
try std.testing.expectEqual(490, energy.value);
|
try std.testing.expectEqual(490, energy.value());
|
||||||
try std.testing.expect(Energy.dims.eql(@TypeOf(energy).dims));
|
try std.testing.expect(Energy.dims.eql(@TypeOf(energy).dims));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "BaseQuantities - Electric combinations" {
|
test "BaseQuantities - Electric combinations" {
|
||||||
const current = ElectricCurrent.Of(f32){ .value = 2.0 }; // 2 A
|
const current = ElectricCurrent.Of(f32).splat(2); // 2 A
|
||||||
const time = Second.Of(f32){ .value = 3.0 }; // 3 s
|
const time = Second.Of(f32).splat(3.0); // 3 s
|
||||||
|
|
||||||
// Charge = Current * time
|
// Charge = Current * time
|
||||||
const charge = current.mul(time);
|
const charge = current.mul(time);
|
||||||
try std.testing.expectEqual(6.0, charge.value);
|
try std.testing.expectEqual(6.0, charge.value());
|
||||||
try std.testing.expect(ElectricCharge.dims.eql(@TypeOf(charge).dims));
|
try std.testing.expect(ElectricCharge.dims.eql(@TypeOf(charge).dims));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Constants - Initialization and dimension checks" {
|
test "Constants - Initialization and dimension checks" {
|
||||||
// Speed of Light
|
// Speed of Light
|
||||||
const c = Constants.SpeedOfLight.Of(f64);
|
const c = Constants.SpeedOfLight.Of(f64);
|
||||||
try std.testing.expectEqual(299792458.0, c.value);
|
try std.testing.expectEqual(299792458.0, c.value());
|
||||||
try std.testing.expectEqual(1, @TypeOf(c).dims.get(.L));
|
try std.testing.expectEqual(1, @TypeOf(c).dims.get(.L));
|
||||||
try std.testing.expectEqual(-1, @TypeOf(c).dims.get(.T));
|
try std.testing.expectEqual(-1, @TypeOf(c).dims.get(.T));
|
||||||
|
|
||||||
// Electron Mass (verifying scale as well)
|
// Electron Mass (verifying scale as well)
|
||||||
const me = Constants.ElectronMass.Of(f64);
|
const me = Constants.ElectronMass.Of(f64);
|
||||||
try std.testing.expectEqual(9.1093837139e-31, me.value);
|
try std.testing.expectEqual(9.1093837139e-31, me.value());
|
||||||
try std.testing.expectEqual(1, @TypeOf(me).dims.get(.M));
|
try std.testing.expectEqual(1, @TypeOf(me).dims.get(.M));
|
||||||
try std.testing.expectEqual(.k, @TypeOf(me).scales.get(.M)); // Should be scaled to kg
|
try std.testing.expectEqual(.k, @TypeOf(me).scales.get(.M)); // Should be scaled to kg
|
||||||
|
|
||||||
// Boltzmann Constant (Complex derived dimensions)
|
// Boltzmann Constant (Complex derived dimensions)
|
||||||
const kb = Constants.Boltzmann.Of(f64);
|
const kb = Constants.Boltzmann.Of(f64);
|
||||||
try std.testing.expectEqual(1.380649e-23, kb.value);
|
try std.testing.expectEqual(1.380649e-23, kb.value());
|
||||||
try std.testing.expectEqual(1, @TypeOf(kb).dims.get(.M));
|
try std.testing.expectEqual(1, @TypeOf(kb).dims.get(.M));
|
||||||
try std.testing.expectEqual(2, @TypeOf(kb).dims.get(.L));
|
try std.testing.expectEqual(2, @TypeOf(kb).dims.get(.L));
|
||||||
try std.testing.expectEqual(-2, @TypeOf(kb).dims.get(.T));
|
try std.testing.expectEqual(-2, @TypeOf(kb).dims.get(.T));
|
||||||
@ -237,7 +237,7 @@ test "Constants - Initialization and dimension checks" {
|
|||||||
|
|
||||||
// Vacuum Permittivity
|
// Vacuum Permittivity
|
||||||
const eps0 = Constants.VacuumPermittivity.Of(f64);
|
const eps0 = Constants.VacuumPermittivity.Of(f64);
|
||||||
try std.testing.expectEqual(8.8541878188e-12, eps0.value);
|
try std.testing.expectEqual(8.8541878188e-12, eps0.value());
|
||||||
try std.testing.expectEqual(-1, @TypeOf(eps0).dims.get(.M));
|
try std.testing.expectEqual(-1, @TypeOf(eps0).dims.get(.M));
|
||||||
try std.testing.expectEqual(-3, @TypeOf(eps0).dims.get(.L));
|
try std.testing.expectEqual(-3, @TypeOf(eps0).dims.get(.L));
|
||||||
try std.testing.expectEqual(4, @TypeOf(eps0).dims.get(.T));
|
try std.testing.expectEqual(4, @TypeOf(eps0).dims.get(.T));
|
||||||
@ -245,7 +245,7 @@ test "Constants - Initialization and dimension checks" {
|
|||||||
|
|
||||||
// Fine Structure Constant (Dimensionless)
|
// Fine Structure Constant (Dimensionless)
|
||||||
const alpha = Constants.FineStructure.Of(f64);
|
const alpha = Constants.FineStructure.Of(f64);
|
||||||
try std.testing.expectEqual(0.0072973525643, alpha.value);
|
try std.testing.expectEqual(0.0072973525643, alpha.value());
|
||||||
try std.testing.expectEqual(0, @TypeOf(alpha).dims.get(.M));
|
try std.testing.expectEqual(0, @TypeOf(alpha).dims.get(.M));
|
||||||
try std.testing.expectEqual(0, @TypeOf(alpha).dims.get(.L));
|
try std.testing.expectEqual(0, @TypeOf(alpha).dims.get(.L));
|
||||||
}
|
}
|
||||||
|
|||||||
1259
src/Quantity.zig
Normal file
1259
src/Quantity.zig
Normal file
File diff suppressed because it is too large
Load Diff
817
src/Scalar.zig
817
src/Scalar.zig
@ -1,817 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const hlp = @import("helper.zig");
|
|
||||||
|
|
||||||
const Vector = @import("Vector.zig").Vector;
|
|
||||||
const Scales = @import("Scales.zig");
|
|
||||||
const UnitScale = Scales.UnitScale;
|
|
||||||
const Dimensions = @import("Dimensions.zig");
|
|
||||||
const Dimension = Dimensions.Dimension;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// 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_opt: Dimensions.ArgOpts, comptime s_opt: Scales.ArgOpts) type {
|
|
||||||
@setEvalBranchQuota(10_000_000);
|
|
||||||
return struct {
|
|
||||||
value: T,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
/// Type of Vector(3, Self)
|
|
||||||
pub const Vec3: type = Vector(3, Self);
|
|
||||||
|
|
||||||
/// Type of underline value, mostly use for Vector
|
|
||||||
pub const ValueType: type = T;
|
|
||||||
pub const dims: Dimensions = Dimensions.init(d_opt);
|
|
||||||
pub const scales = Scales.init(s_opt);
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
// Internal: resolved-rhs shorthands
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Scalar type that `rhs` normalises to (bare numbers → dimensionless).
|
|
||||||
inline fn RhsT(comptime Rhs: type) type {
|
|
||||||
return hlp.rhsScalarType(T, Rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Normalise `rhs` (bare number or Scalar) into a proper Scalar value.
|
|
||||||
inline fn rhs(r: anytype) RhsT(@TypeOf(r)) {
|
|
||||||
return hlp.toRhsScalar(T, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
// Arithmetic
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Add two quantities. Dimensions must match — compile error otherwise.
|
|
||||||
/// Scales are auto-resolved to the finer of the two.
|
|
||||||
/// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`
|
|
||||||
/// (bare numbers are treated as dimensionless).
|
|
||||||
pub inline fn add(self: Self, r: anytype) Scalar(
|
|
||||||
T,
|
|
||||||
dims.argsOpt(),
|
|
||||||
hlp.finerScales(Self, RhsT(@TypeOf(r))).argsOpt(),
|
|
||||||
) {
|
|
||||||
const rhs_s = rhs(r);
|
|
||||||
const RhsType = @TypeOf(rhs_s);
|
|
||||||
if (comptime !dims.eql(RhsType.dims))
|
|
||||||
@compileError("Dimension mismatch in add: " ++ dims.str() ++ " vs " ++ RhsType.dims.str());
|
|
||||||
if (comptime RhsType == Self)
|
|
||||||
return .{ .value = self.value + rhs_s.value };
|
|
||||||
|
|
||||||
const TargetType = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const lhs_val = if (comptime Self == TargetType) self.value else self.to(TargetType).value;
|
|
||||||
const rhs_val = if (comptime RhsType == TargetType) rhs_s.value else rhs_s.to(TargetType).value;
|
|
||||||
return .{ .value = if (comptime hlp.isInt(T)) lhs_val +| rhs_val else lhs_val + rhs_val };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subtract two quantities. Dimensions must match — compile error otherwise.
|
|
||||||
/// Scales are auto-resolved to the finer of the two.
|
|
||||||
/// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`.
|
|
||||||
pub inline fn sub(self: Self, r: anytype) Scalar(
|
|
||||||
T,
|
|
||||||
dims.argsOpt(),
|
|
||||||
hlp.finerScales(Self, RhsT(@TypeOf(r))).argsOpt(),
|
|
||||||
) {
|
|
||||||
const rhs_s = rhs(r);
|
|
||||||
const RhsType = @TypeOf(rhs_s);
|
|
||||||
if (comptime !dims.eql(RhsType.dims))
|
|
||||||
@compileError("Dimension mismatch in sub: " ++ dims.str() ++ " vs " ++ RhsType.dims.str());
|
|
||||||
if (comptime RhsType == Self)
|
|
||||||
return .{ .value = self.value - rhs_s.value };
|
|
||||||
|
|
||||||
const TargetType = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const lhs_val = if (comptime Self == TargetType) self.value else self.to(TargetType).value;
|
|
||||||
const rhs_val = if (comptime RhsType == TargetType) rhs_s.value else rhs_s.to(TargetType).value;
|
|
||||||
return .{ .value = if (comptime hlp.isInt(T)) lhs_val -| rhs_val else lhs_val - rhs_val };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Multiply two quantities. Dimension exponents are summed: `L¹ * T⁻¹ → L¹T⁻¹`.
|
|
||||||
/// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`
|
|
||||||
/// (bare numbers are treated as dimensionless — dimensions pass through unchanged).
|
|
||||||
pub inline fn mul(self: Self, r: anytype) Scalar(
|
|
||||||
T,
|
|
||||||
dims.add(RhsT(@TypeOf(r)).dims).argsOpt(),
|
|
||||||
hlp.finerScales(Self, RhsT(@TypeOf(r))).argsOpt(),
|
|
||||||
) {
|
|
||||||
const rhs_s = rhs(r);
|
|
||||||
const RhsType = @TypeOf(rhs_s);
|
|
||||||
const SelfNorm = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const RhsNorm = Scalar(T, RhsType.dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
if (comptime Self == SelfNorm and RhsType == RhsNorm)
|
|
||||||
return .{ .value = self.value * rhs_s.value };
|
|
||||||
|
|
||||||
const lhs_val = if (comptime Self == SelfNorm) self.value else self.to(SelfNorm).value;
|
|
||||||
const rhs_val = if (comptime RhsType == RhsNorm) rhs_s.value else rhs_s.to(RhsNorm).value;
|
|
||||||
return .{ .value = if (comptime hlp.isInt(T)) lhs_val *| rhs_val else lhs_val * rhs_val };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Divide two quantities. Dimension exponents are subtracted: `L¹ / T¹ → L¹T⁻¹`.
|
|
||||||
/// Integer types use truncating division.
|
|
||||||
/// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`.
|
|
||||||
pub inline fn div(self: Self, r: anytype) Scalar(
|
|
||||||
T,
|
|
||||||
dims.sub(RhsT(@TypeOf(r)).dims).argsOpt(),
|
|
||||||
hlp.finerScales(Self, RhsT(@TypeOf(r))).argsOpt(),
|
|
||||||
) {
|
|
||||||
const rhs_s = rhs(r);
|
|
||||||
const RhsType = @TypeOf(rhs_s);
|
|
||||||
const SelfNorm = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const RhsNorm = Scalar(T, RhsType.dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const lhs_val = if (comptime Self == SelfNorm) self.value else self.to(SelfNorm).value;
|
|
||||||
const rhs_val = if (comptime RhsType == RhsNorm) rhs_s.value else rhs_s.to(RhsNorm).value;
|
|
||||||
if (comptime hlp.isInt(T)) {
|
|
||||||
return .{ .value = @divTrunc(lhs_val, rhs_val) };
|
|
||||||
} else {
|
|
||||||
return .{ .value = lhs_val / rhs_val };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
// Unary
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Returns the absolute value of the quantity.
|
|
||||||
/// Dimensions and scales remain entirely unchanged.
|
|
||||||
pub inline fn abs(self: Self) Self {
|
|
||||||
if (comptime @typeInfo(T) == .int)
|
|
||||||
return .{ .value = @intCast(@abs(self.value)) }
|
|
||||||
else
|
|
||||||
return .{ .value = @abs(self.value) };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(
|
|
||||||
T,
|
|
||||||
dims.scale(exp).argsOpt(),
|
|
||||||
scales.argsOpt(),
|
|
||||||
) {
|
|
||||||
if (comptime hlp.isInt(T))
|
|
||||||
return .{ .value = std.math.powi(T, self.value, exp) catch std.math.maxInt(T) }
|
|
||||||
else
|
|
||||||
return .{ .value = std.math.pow(T, self.value, @as(T, @floatFromInt(exp))) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn sqrt(self: Self) Scalar(
|
|
||||||
T,
|
|
||||||
dims.div(2).argsOpt(),
|
|
||||||
scales.argsOpt(),
|
|
||||||
) {
|
|
||||||
if (comptime !dims.isSquare()) // Check if all exponents are divisible by 2
|
|
||||||
@compileError("Cannot take sqrt of " ++ dims.str() ++ ": exponents must be even.");
|
|
||||||
if (self.value < 0) return .{ .value = 0 };
|
|
||||||
|
|
||||||
if (comptime hlp.isInt(T)) {
|
|
||||||
const UnsignedT = @Int(.unsigned, @typeInfo(T).int.bits);
|
|
||||||
const u_len_sq = @as(UnsignedT, @intCast(self.value));
|
|
||||||
return .{ .value = @as(T, @intCast(std.math.sqrt(u_len_sq))) };
|
|
||||||
} else {
|
|
||||||
return .{ .value = @sqrt(self.value) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
// Conversion
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Convert to a compatible unit type. The scale ratio is computed at comptime.
|
|
||||||
/// Compile error if dimensions don't match.
|
|
||||||
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 @TypeOf(self) == Dest)
|
|
||||||
return self;
|
|
||||||
|
|
||||||
const DestT = Dest.ValueType;
|
|
||||||
const ratio = comptime (scales.getFactor(dims) / Dest.scales.getFactor(Dest.dims));
|
|
||||||
|
|
||||||
// Fast-path: Native pure-integer exact conversions
|
|
||||||
if (comptime @typeInfo(T) == .int and @typeInfo(DestT) == .int) {
|
|
||||||
if (comptime ratio >= 1.0 and @round(ratio) == ratio) {
|
|
||||||
const mult: DestT = comptime @intFromFloat(ratio);
|
|
||||||
return .{ .value = @as(DestT, @intCast(self.value)) * mult };
|
|
||||||
} else if (comptime ratio < 1.0 and @round(1.0 / ratio) == 1.0 / ratio) {
|
|
||||||
const d: DestT = comptime @intFromFloat(1.0 / ratio);
|
|
||||||
const val = @as(DestT, @intCast(self.value));
|
|
||||||
const half = comptime d / 2;
|
|
||||||
const rounded = if (val >= 0) @divTrunc(val + half, d) else @divTrunc(val - half, d);
|
|
||||||
return .{ .value = rounded };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback preserving native Float types (e.g., f128 shouldn't downcast to f64)
|
|
||||||
if (comptime @typeInfo(DestT) == .float) {
|
|
||||||
const val_f = switch (@typeInfo(T)) {
|
|
||||||
inline .int => @as(DestT, @floatFromInt(self.value)),
|
|
||||||
inline .float => @as(DestT, @floatCast(self.value)),
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
return .{ .value = val_f * @as(DestT, @floatCast(ratio)) };
|
|
||||||
} else {
|
|
||||||
const val_f = switch (@typeInfo(T)) {
|
|
||||||
inline .int => @as(f64, @floatFromInt(self.value)),
|
|
||||||
inline .float => @as(f64, @floatCast(self.value)),
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
return .{ .value = @intFromFloat(@round(val_f * ratio)) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
// Comparisons
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Compares two Scalar for exact equality.
|
|
||||||
/// Dimensions must match — compile error otherwise. Scales are auto-resolved.
|
|
||||||
/// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`.
|
|
||||||
pub inline fn eq(self: Self, r: anytype) bool {
|
|
||||||
const rhs_s = rhs(r);
|
|
||||||
const RhsType = @TypeOf(rhs_s);
|
|
||||||
if (comptime !dims.eql(RhsType.dims))
|
|
||||||
@compileError("Dimension mismatch in eq: " ++ dims.str() ++ " vs " ++ RhsType.dims.str());
|
|
||||||
if (comptime RhsType == Self)
|
|
||||||
return self.value == rhs_s.value;
|
|
||||||
|
|
||||||
const TargetType = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const lhs_val = if (comptime Self == TargetType) self.value else self.to(TargetType).value;
|
|
||||||
const rhs_val = if (comptime RhsType == TargetType) rhs_s.value else rhs_s.to(TargetType).value;
|
|
||||||
return lhs_val == rhs_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compares two quantities for inequality.
|
|
||||||
/// Dimensions must match — compile error otherwise. Scales are auto-resolved.
|
|
||||||
/// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`.
|
|
||||||
pub inline fn ne(self: Self, r: anytype) bool {
|
|
||||||
const rhs_s = rhs(r);
|
|
||||||
const RhsType = @TypeOf(rhs_s);
|
|
||||||
if (comptime !dims.eql(RhsType.dims))
|
|
||||||
@compileError("Dimension mismatch in ne: " ++ dims.str() ++ " vs " ++ RhsType.dims.str());
|
|
||||||
if (comptime RhsType == Self)
|
|
||||||
return self.value != rhs_s.value;
|
|
||||||
|
|
||||||
const TargetType = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const lhs_val = if (comptime Self == TargetType) self.value else self.to(TargetType).value;
|
|
||||||
const rhs_val = if (comptime RhsType == TargetType) rhs_s.value else rhs_s.to(TargetType).value;
|
|
||||||
return lhs_val != rhs_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if this quantity is strictly greater than the right-hand side.
|
|
||||||
/// Dimensions must match — compile error otherwise. Scales are auto-resolved.
|
|
||||||
/// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`.
|
|
||||||
pub inline fn gt(self: Self, r: anytype) bool {
|
|
||||||
const rhs_s = rhs(r);
|
|
||||||
const RhsType = @TypeOf(rhs_s);
|
|
||||||
if (comptime !dims.eql(RhsType.dims))
|
|
||||||
@compileError("Dimension mismatch in gt: " ++ dims.str() ++ " vs " ++ RhsType.dims.str());
|
|
||||||
if (comptime RhsType == Self)
|
|
||||||
return self.value > rhs_s.value;
|
|
||||||
|
|
||||||
const TargetType = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const lhs_val = if (comptime Self == TargetType) self.value else self.to(TargetType).value;
|
|
||||||
const rhs_val = if (comptime RhsType == TargetType) rhs_s.value else rhs_s.to(TargetType).value;
|
|
||||||
return lhs_val > rhs_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if this quantity is greater than or equal to the right-hand side.
|
|
||||||
/// Dimensions must match — compile error otherwise. Scales are auto-resolved.
|
|
||||||
/// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`.
|
|
||||||
pub inline fn gte(self: Self, r: anytype) bool {
|
|
||||||
const rhs_s = rhs(r);
|
|
||||||
const RhsType = @TypeOf(rhs_s);
|
|
||||||
if (comptime !dims.eql(RhsType.dims))
|
|
||||||
@compileError("Dimension mismatch in gte: " ++ dims.str() ++ " vs " ++ RhsType.dims.str());
|
|
||||||
if (comptime RhsType == Self)
|
|
||||||
return self.value >= rhs_s.value;
|
|
||||||
|
|
||||||
const TargetType = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const lhs_val = if (comptime Self == TargetType) self.value else self.to(TargetType).value;
|
|
||||||
const rhs_val = if (comptime RhsType == TargetType) rhs_s.value else rhs_s.to(TargetType).value;
|
|
||||||
return lhs_val >= rhs_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if this quantity is strictly less than the right-hand side.
|
|
||||||
/// Dimensions must match — compile error otherwise. Scales are auto-resolved.
|
|
||||||
/// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`.
|
|
||||||
pub inline fn lt(self: Self, r: anytype) bool {
|
|
||||||
const rhs_s = rhs(r);
|
|
||||||
const RhsType = @TypeOf(rhs_s);
|
|
||||||
if (comptime !dims.eql(RhsType.dims))
|
|
||||||
@compileError("Dimension mismatch in lt: " ++ dims.str() ++ " vs " ++ RhsType.dims.str());
|
|
||||||
if (comptime RhsType == Self)
|
|
||||||
return self.value < rhs_s.value;
|
|
||||||
|
|
||||||
const TargetType = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const lhs_val = if (comptime Self == TargetType) self.value else self.to(TargetType).value;
|
|
||||||
const rhs_val = if (comptime RhsType == TargetType) rhs_s.value else rhs_s.to(TargetType).value;
|
|
||||||
return lhs_val < rhs_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if this quantity is less than or equal to the right-hand side.
|
|
||||||
/// Dimensions must match — compile error otherwise. Scales are auto-resolved.
|
|
||||||
/// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`.
|
|
||||||
pub inline fn lte(self: Self, r: anytype) bool {
|
|
||||||
const rhs_s = rhs(r);
|
|
||||||
const RhsType = @TypeOf(rhs_s);
|
|
||||||
if (comptime !dims.eql(RhsType.dims))
|
|
||||||
@compileError("Dimension mismatch in lte: " ++ dims.str() ++ " vs " ++ RhsType.dims.str());
|
|
||||||
if (comptime RhsType == Self)
|
|
||||||
return self.value <= rhs_s.value;
|
|
||||||
|
|
||||||
const TargetType = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
|
|
||||||
const lhs_val = if (comptime Self == TargetType) self.value else self.to(TargetType).value;
|
|
||||||
const rhs_val = if (comptime RhsType == TargetType) rhs_s.value else rhs_s.to(TargetType).value;
|
|
||||||
return lhs_val <= rhs_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
// Vector helpers
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Return a `Vector(len, Self)` type.
|
|
||||||
pub fn Vec(_: Self, comptime len: comptime_int) type {
|
|
||||||
return Vector(len, Self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a `Vector(len, Self)` with all components set to this value.
|
|
||||||
pub fn vec(self: Self, comptime len: comptime_int) Vector(len, Self) {
|
|
||||||
return Vector(len, Self).initDefault(self.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand for `Vec(3)` — wrap this value into a 3-component vector.
|
|
||||||
pub fn vec3(self: Self) Vec3 {
|
|
||||||
return Vec3.initDefault(self.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
// Formatting
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
pub fn formatNumber(
|
|
||||||
self: Self,
|
|
||||||
writer: *std.Io.Writer,
|
|
||||||
options: std.fmt.Number,
|
|
||||||
) !void {
|
|
||||||
switch (@typeInfo(T)) {
|
|
||||||
.float, .comptime_float => try writer.printFloat(self.value, options),
|
|
||||||
.int, .comptime_int => try writer.printInt(self.value, 10, .lower, .{
|
|
||||||
.width = options.width,
|
|
||||||
.alignment = options.alignment,
|
|
||||||
.fill = options.fill,
|
|
||||||
.precision = options.precision,
|
|
||||||
}),
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
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 "Generate quantity" {
|
|
||||||
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 };
|
|
||||||
|
|
||||||
try std.testing.expectEqual(10, distance.value);
|
|
||||||
try std.testing.expectEqual(2, time.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Comparisons (eq, ne, gt, gte, lt, lte)" {
|
|
||||||
const Meter = Scalar(i128, .{ .L = 1 }, .{});
|
|
||||||
const KiloMeter = Scalar(i128, .{ .L = 1 }, .{ .L = .k });
|
|
||||||
|
|
||||||
const m1000 = Meter{ .value = 1000 };
|
|
||||||
const km1 = KiloMeter{ .value = 1 };
|
|
||||||
const km2 = KiloMeter{ .value = 2 };
|
|
||||||
|
|
||||||
// Equal / Not Equal
|
|
||||||
try std.testing.expect(m1000.eq(km1));
|
|
||||||
try std.testing.expect(km1.eq(m1000));
|
|
||||||
try std.testing.expect(km2.ne(m1000));
|
|
||||||
|
|
||||||
// Greater Than / Greater Than or Equal
|
|
||||||
try std.testing.expect(km2.gt(m1000));
|
|
||||||
try std.testing.expect(km2.gt(km1));
|
|
||||||
try std.testing.expect(km1.gte(m1000));
|
|
||||||
try std.testing.expect(km2.gte(m1000));
|
|
||||||
|
|
||||||
// Less Than / Less Than or Equal
|
|
||||||
try std.testing.expect(m1000.lt(km2));
|
|
||||||
try std.testing.expect(km1.lt(km2));
|
|
||||||
try std.testing.expect(km1.lte(m1000));
|
|
||||||
try std.testing.expect(m1000.lte(km2));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Add" {
|
|
||||||
const Meter = Scalar(i128, .{ .L = 1 }, .{});
|
|
||||||
|
|
||||||
const distance = Meter{ .value = 10 };
|
|
||||||
const distance2 = Meter{ .value = 20 };
|
|
||||||
|
|
||||||
const added = distance.add(distance2);
|
|
||||||
try std.testing.expectEqual(30, added.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(added).dims.get(.L));
|
|
||||||
|
|
||||||
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);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(added2).dims.get(.L));
|
|
||||||
|
|
||||||
const added3 = distance3.add(distance).to(KiloMeter);
|
|
||||||
try std.testing.expectEqual(2, added3.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(added3).dims.get(.L));
|
|
||||||
|
|
||||||
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);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(added4).dims.get(.L));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Sub" {
|
|
||||||
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 };
|
|
||||||
const diff = a.sub(b);
|
|
||||||
try std.testing.expectEqual(300, diff.value);
|
|
||||||
const diff2 = b.sub(a);
|
|
||||||
try std.testing.expectEqual(-300, diff2.value);
|
|
||||||
|
|
||||||
const km_f = KiloMeter_f{ .value = 2.5 };
|
|
||||||
const m_f = Meter{ .value = 500 };
|
|
||||||
const diff3 = km_f.sub(m_f);
|
|
||||||
try std.testing.expectApproxEqAbs(2000, diff3.value, 1e-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "MulBy" {
|
|
||||||
const Meter = Scalar(i128, .{ .L = 1 }, .{});
|
|
||||||
const Second = Scalar(f32, .{ .T = 1 }, .{});
|
|
||||||
|
|
||||||
const d = Meter{ .value = 3.0 };
|
|
||||||
const t = Second{ .value = 4.0 };
|
|
||||||
|
|
||||||
const area_time = d.mul(t);
|
|
||||||
try std.testing.expectEqual(12, area_time.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L));
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.T));
|
|
||||||
|
|
||||||
const d2 = Meter{ .value = 5.0 };
|
|
||||||
const area = d.mul(d2);
|
|
||||||
try std.testing.expectEqual(15, area.value);
|
|
||||||
try std.testing.expectEqual(2, @TypeOf(area).dims.get(.L));
|
|
||||||
try std.testing.expectEqual(0, @TypeOf(area).dims.get(.T));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "MulBy with scale" {
|
|
||||||
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 };
|
|
||||||
const prod = dist.mul(mass);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.L));
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.M));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "MulBy with type change" {
|
|
||||||
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 };
|
|
||||||
|
|
||||||
const area_time = d.mul(t).to(KmSec);
|
|
||||||
const area_time_f = d.mul(t).to(KmSec_f);
|
|
||||||
try std.testing.expectEqual(12, area_time.value);
|
|
||||||
try std.testing.expectApproxEqAbs(12, area_time_f.value, 0.0001);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L));
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.T));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "MulBy small" {
|
|
||||||
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 };
|
|
||||||
|
|
||||||
const area_time = d.mul(t);
|
|
||||||
try std.testing.expectEqual(12, area_time.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L));
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.T));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "MulBy dimensionless" {
|
|
||||||
const DimLess = Scalar(i128, .{}, .{});
|
|
||||||
const Meter = Scalar(i128, .{ .L = 1 }, .{});
|
|
||||||
|
|
||||||
const d = Meter{ .value = 7 };
|
|
||||||
const scaled = d.mul(DimLess{ .value = 3 });
|
|
||||||
try std.testing.expectEqual(21, scaled.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Sqrt" {
|
|
||||||
const MeterSquare = Scalar(i128, .{ .L = 2 }, .{});
|
|
||||||
|
|
||||||
var d = MeterSquare{ .value = 9 };
|
|
||||||
var scaled = d.sqrt();
|
|
||||||
try std.testing.expectEqual(3, scaled.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
|
||||||
|
|
||||||
d = MeterSquare{ .value = -5 };
|
|
||||||
scaled = d.sqrt();
|
|
||||||
try std.testing.expectEqual(0, scaled.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
|
||||||
|
|
||||||
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);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(scaled2).dims.get(.L));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Chained: velocity and acceleration" {
|
|
||||||
const Meter = Scalar(i128, .{ .L = 1 }, .{});
|
|
||||||
const Second = Scalar(f32, .{ .T = 1 }, .{});
|
|
||||||
|
|
||||||
const dist = Meter{ .value = 100.0 };
|
|
||||||
const t1 = Second{ .value = 5.0 };
|
|
||||||
const velocity = dist.div(t1);
|
|
||||||
try std.testing.expectEqual(20, velocity.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(velocity).dims.get(.L));
|
|
||||||
try std.testing.expectEqual(-1, @TypeOf(velocity).dims.get(.T));
|
|
||||||
|
|
||||||
const t2 = Second{ .value = 4.0 };
|
|
||||||
const accel = velocity.div(t2);
|
|
||||||
try std.testing.expectEqual(5, accel.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(accel).dims.get(.L));
|
|
||||||
try std.testing.expectEqual(-2, @TypeOf(accel).dims.get(.T));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "DivBy integer exact" {
|
|
||||||
const Meter = Scalar(i128, .{ .L = 1 }, .{});
|
|
||||||
const Second = Scalar(f32, .{ .T = 1 }, .{});
|
|
||||||
|
|
||||||
const dist = Meter{ .value = 120 };
|
|
||||||
const time = Second{ .value = 4 };
|
|
||||||
const vel = dist.div(time);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(30, vel.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(vel).dims.get(.L));
|
|
||||||
try std.testing.expectEqual(-1, @TypeOf(vel).dims.get(.T));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Finer scales skip dim 0" {
|
|
||||||
const Dimless = Scalar(i128, .{}, .{});
|
|
||||||
const KiloMetre = Scalar(i128, .{ .L = 1 }, .{ .L = .k });
|
|
||||||
|
|
||||||
const r = Dimless{ .value = 30 };
|
|
||||||
const time = KiloMetre{ .value = 4 };
|
|
||||||
const vel = r.mul(time);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(120, vel.value);
|
|
||||||
try std.testing.expectEqual(Scales.UnitScale.k, @TypeOf(vel).scales.get(.L));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Conversion chain: km -> m -> cm" {
|
|
||||||
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);
|
|
||||||
const cm = m.to(CentiMeter);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(15_000, m.value);
|
|
||||||
try std.testing.expectEqual(1_500_000, cm.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Conversion: hours -> minutes -> seconds" {
|
|
||||||
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);
|
|
||||||
const sec = min.to(Second);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(60, min.value);
|
|
||||||
try std.testing.expectEqual(3600, sec.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Negative values" {
|
|
||||||
const Meter = Scalar(i128, .{ .L = 1 }, .{});
|
|
||||||
|
|
||||||
const a = Meter{ .value = 5 };
|
|
||||||
const b = Meter{ .value = 20 };
|
|
||||||
const diff = a.sub(b);
|
|
||||||
try std.testing.expectEqual(-15, diff.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Format Scalar" {
|
|
||||||
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 };
|
|
||||||
const momentum = KgMeterPerSecond{ .value = 42.0 };
|
|
||||||
|
|
||||||
var buf: [64]u8 = undefined;
|
|
||||||
var res = try std.fmt.bufPrint(&buf, "{d:.2}", .{m});
|
|
||||||
try std.testing.expectEqualStrings("1.23m", res);
|
|
||||||
|
|
||||||
res = try std.fmt.bufPrint(&buf, "{d}", .{accel});
|
|
||||||
try std.testing.expectEqualStrings("9.81m.ns⁻²", res);
|
|
||||||
|
|
||||||
res = try std.fmt.bufPrint(&buf, "{d}", .{momentum});
|
|
||||||
try std.testing.expectEqualStrings("42m.kg.s⁻¹", res);
|
|
||||||
|
|
||||||
res = try std.fmt.bufPrint(&buf, "{d:_>10.1}", .{m});
|
|
||||||
try std.testing.expectEqualStrings("_______1.2m", res);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Abs" {
|
|
||||||
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, .{ .L = 1 }, .{});
|
|
||||||
const m3 = m_float{ .value = -42.5 };
|
|
||||||
try std.testing.expectEqual(42.5, m3.abs().value);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Pow" {
|
|
||||||
const Meter = Scalar(i128, .{ .L = 1 }, .{});
|
|
||||||
const d = Meter{ .value = 4 };
|
|
||||||
|
|
||||||
const area = d.pow(2);
|
|
||||||
try std.testing.expectEqual(16, area.value);
|
|
||||||
try std.testing.expectEqual(2, @TypeOf(area).dims.get(.L));
|
|
||||||
|
|
||||||
const volume = d.pow(3);
|
|
||||||
try std.testing.expectEqual(64, volume.value);
|
|
||||||
try std.testing.expectEqual(3, @TypeOf(volume).dims.get(.L));
|
|
||||||
|
|
||||||
// Float test
|
|
||||||
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);
|
|
||||||
try std.testing.expectEqual(3, @TypeOf(area_f).dims.get(.L));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "mul comptime_int" {
|
|
||||||
const Meter = Scalar(i128, .{ .L = 1 }, .{});
|
|
||||||
const d = Meter{ .value = 7 };
|
|
||||||
|
|
||||||
const scaled = d.mul(3); // comptime_int → dimensionless
|
|
||||||
try std.testing.expectEqual(21, scaled.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
|
||||||
try std.testing.expectEqual(0, @TypeOf(scaled).dims.get(.T));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "mul comptime_float" {
|
|
||||||
const MeterF = Scalar(f64, .{ .L = 1 }, .{});
|
|
||||||
const d = MeterF{ .value = 4.0 };
|
|
||||||
|
|
||||||
const scaled = d.mul(2.5); // comptime_float → dimensionless
|
|
||||||
try std.testing.expectApproxEqAbs(10.0, scaled.value, 1e-9);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "mul T (value type)" {
|
|
||||||
const MeterF = Scalar(f32, .{ .L = 1 }, .{});
|
|
||||||
const d = MeterF{ .value = 6.0 };
|
|
||||||
const factor: f32 = 0.5;
|
|
||||||
|
|
||||||
const scaled = d.mul(factor); // bare f32 → dimensionless
|
|
||||||
try std.testing.expectApproxEqAbs(3.0, scaled.value, 1e-6);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "div comptime_int" {
|
|
||||||
const Meter = Scalar(i128, .{ .L = 1 }, .{});
|
|
||||||
const d = Meter{ .value = 100 };
|
|
||||||
|
|
||||||
const half = d.div(4); // comptime_int → dimensionless divisor
|
|
||||||
try std.testing.expectEqual(25, half.value);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(half).dims.get(.L));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "div comptime_float" {
|
|
||||||
const MeterF = Scalar(f64, .{ .L = 1 }, .{});
|
|
||||||
const d = MeterF{ .value = 9.0 };
|
|
||||||
|
|
||||||
const r = d.div(3.0);
|
|
||||||
try std.testing.expectApproxEqAbs(3.0, r.value, 1e-9);
|
|
||||||
try std.testing.expectEqual(1, @TypeOf(r).dims.get(.L));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "add/sub bare number on dimensionless scalar" {
|
|
||||||
// Bare numbers are dimensionless, so add/sub only works when Self is also dimensionless.
|
|
||||||
const DimLess = Scalar(i128, .{}, .{});
|
|
||||||
const a = DimLess{ .value = 10 };
|
|
||||||
|
|
||||||
const b = a.add(5); // comptime_int, both dimensionless → ok
|
|
||||||
try std.testing.expectEqual(15, b.value);
|
|
||||||
|
|
||||||
const c = a.sub(3);
|
|
||||||
try std.testing.expectEqual(7, c.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Imperial length scales" {
|
|
||||||
const Foot = Scalar(f64, .{ .L = 1 }, .{ .L = .ft });
|
|
||||||
const Meter = Scalar(f64, .{ .L = 1 }, .{});
|
|
||||||
const Inch = Scalar(f64, .{ .L = 1 }, .{ .L = .inch });
|
|
||||||
const CentiMeter = Scalar(f64, .{ .L = 1 }, .{ .L = .c });
|
|
||||||
const Mile = Scalar(f64, .{ .L = 1 }, .{ .L = .mi });
|
|
||||||
const KiloMeter = Scalar(f64, .{ .L = 1 }, .{ .L = .k });
|
|
||||||
const Yard = Scalar(f64, .{ .L = 1 }, .{ .L = .yd });
|
|
||||||
|
|
||||||
// 1 ft → 0.3048 m
|
|
||||||
const one_ft = Foot{ .value = 1.0 };
|
|
||||||
try std.testing.expectApproxEqAbs(0.3048, one_ft.to(Meter).value, 1e-9);
|
|
||||||
|
|
||||||
// 12 in → 1 ft
|
|
||||||
const twelve_in = Inch{ .value = 12.0 };
|
|
||||||
try std.testing.expectApproxEqAbs(1.0, twelve_in.to(Foot).value, 1e-9);
|
|
||||||
|
|
||||||
// 1 in → 2.54 cm
|
|
||||||
const one_in = Inch{ .value = 1.0 };
|
|
||||||
try std.testing.expectApproxEqAbs(2.54, one_in.to(CentiMeter).value, 1e-9);
|
|
||||||
|
|
||||||
// 1 mi → 1.609344 km
|
|
||||||
const one_mi = Mile{ .value = 1.0 };
|
|
||||||
try std.testing.expectApproxEqAbs(1.609344, one_mi.to(KiloMeter).value, 1e-9);
|
|
||||||
|
|
||||||
// 3 ft → 1 yd
|
|
||||||
const three_ft = Foot{ .value = 3.0 };
|
|
||||||
try std.testing.expectApproxEqAbs(1.0, three_ft.to(Yard).value, 1e-9);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Imperial mass scales" {
|
|
||||||
const Pound = Scalar(f64, .{ .M = 1 }, .{ .M = .lb });
|
|
||||||
const KiloGram = Scalar(f64, .{ .M = 1 }, .{ .M = .k });
|
|
||||||
const Ounce = Scalar(f64, .{ .M = 1 }, .{ .M = .oz });
|
|
||||||
const Stone = Scalar(f64, .{ .M = 1 }, .{ .M = .st });
|
|
||||||
|
|
||||||
// 1 lb → ~0.453592 kg
|
|
||||||
const one_lb = Pound{ .value = 1.0 };
|
|
||||||
try std.testing.expectApproxEqAbs(0.45359237, one_lb.to(KiloGram).value, 1e-6);
|
|
||||||
|
|
||||||
// 16 oz → 1 lb
|
|
||||||
const sixteen_oz = Ounce{ .value = 16.0 };
|
|
||||||
try std.testing.expectApproxEqAbs(1.0, sixteen_oz.to(Pound).value, 1e-6);
|
|
||||||
|
|
||||||
// 1 stone → 14 lb
|
|
||||||
const one_st = Stone{ .value = 1.0 };
|
|
||||||
try std.testing.expectApproxEqAbs(14.0, one_st.to(Pound).value, 1e-4);
|
|
||||||
|
|
||||||
// 2 lb + 8 oz → 2.5 lb
|
|
||||||
const two_lb = Pound{ .value = 2.0 };
|
|
||||||
const eight_oz = Ounce{ .value = 8.0 };
|
|
||||||
const total = two_lb.add(eight_oz).to(Pound);
|
|
||||||
try std.testing.expectApproxEqAbs(2.5, total.value, 1e-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "comparisons with comptime_int on dimensionless scalar" {
|
|
||||||
const DimLess = Scalar(i128, .{}, .{});
|
|
||||||
const x = DimLess{ .value = 42 };
|
|
||||||
|
|
||||||
try std.testing.expect(x.eq(42));
|
|
||||||
try std.testing.expect(x.ne(0));
|
|
||||||
try std.testing.expect(x.gt(10));
|
|
||||||
try std.testing.expect(x.gte(42));
|
|
||||||
try std.testing.expect(x.lt(100));
|
|
||||||
try std.testing.expect(x.lte(42));
|
|
||||||
}
|
|
||||||
830
src/Vector.zig
830
src/Vector.zig
@ -1,830 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const hlp = @import("helper.zig");
|
|
||||||
|
|
||||||
const Scalar = @import("Scalar.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,7 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
const Scalar = @import("Scalar.zig").Scalar;
|
const Scalar = @import("Quantity.zig").Scalar;
|
||||||
const Vector = @import("Vector.zig").Vector;
|
const Vector = @import("Quantity.zig").Vector;
|
||||||
|
|
||||||
var io: Io = undefined;
|
var io: Io = undefined;
|
||||||
pub fn main(init: std.process.Init) !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
@ -11,6 +11,17 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
|
|
||||||
io = init.io;
|
io = init.io;
|
||||||
|
|
||||||
|
// try vectorSIMDvsNative(f64, &stdout_writer.interface);
|
||||||
|
// try stdout_writer.flush();
|
||||||
|
// try vectorSIMDvsNative(f32, &stdout_writer.interface);
|
||||||
|
// try stdout_writer.flush();
|
||||||
|
// try vectorSIMDvsNative(i32, &stdout_writer.interface);
|
||||||
|
// try stdout_writer.flush();
|
||||||
|
// try vectorSIMDvsNative(i64, &stdout_writer.interface);
|
||||||
|
// try stdout_writer.flush();
|
||||||
|
// try vectorSIMDvsNative(i128, &stdout_writer.interface);
|
||||||
|
// try stdout_writer.flush();
|
||||||
|
|
||||||
try bench_Scalar(&stdout_writer.interface);
|
try bench_Scalar(&stdout_writer.interface);
|
||||||
try stdout_writer.flush();
|
try stdout_writer.flush();
|
||||||
try bench_vsNative(&stdout_writer.interface);
|
try bench_vsNative(&stdout_writer.interface);
|
||||||
@ -100,23 +111,23 @@ fn bench_Scalar(writer: *std.Io.Writer) !void {
|
|||||||
std.mem.doNotOptimizeAway(
|
std.mem.doNotOptimizeAway(
|
||||||
{
|
{
|
||||||
_ = if (comptime std.mem.eql(u8, op_name, "add"))
|
_ = if (comptime std.mem.eql(u8, op_name, "add"))
|
||||||
(M{ .value = getVal(T, i, 63) }).add(M{ .value = getVal(T, i +% 7, 63) })
|
(M.splat(getVal(T, i, 63))).add(M.splat(getVal(T, i +% 7, 63)))
|
||||||
else if (comptime std.mem.eql(u8, op_name, "sub"))
|
else if (comptime std.mem.eql(u8, op_name, "sub"))
|
||||||
(M{ .value = getVal(T, i +% 10, 63) }).sub(M{ .value = getVal(T, i, 63) })
|
(M.splat(getVal(T, i +% 10, 63))).sub(M.splat(getVal(T, i, 63)))
|
||||||
else if (comptime std.mem.eql(u8, op_name, "mul"))
|
else if (comptime std.mem.eql(u8, op_name, "mul"))
|
||||||
(M{ .value = getVal(T, i, 63) }).mul(M{ .value = getVal(T, i +% 1, 63) })
|
(M.splat(getVal(T, i, 63))).mul(M.splat(getVal(T, i +% 1, 63)))
|
||||||
else if (comptime std.mem.eql(u8, op_name, "div"))
|
else if (comptime std.mem.eql(u8, op_name, "div"))
|
||||||
(M{ .value = getVal(T, i +% 10, 63) }).div(S{ .value = getVal(T, i, 63) })
|
(M.splat(getVal(T, i +% 10, 63))).div(S.splat(getVal(T, i, 63)))
|
||||||
else if (comptime std.mem.eql(u8, op_name, "to"))
|
else if (comptime std.mem.eql(u8, op_name, "to"))
|
||||||
(KM{ .value = getVal(T, i, 15) }).to(M)
|
(KM.splat(getVal(T, i, 15))).to(M)
|
||||||
else if (comptime std.mem.eql(u8, op_name, "abs"))
|
else if (comptime std.mem.eql(u8, op_name, "abs"))
|
||||||
(M{ .value = getVal(T, i, 63) }).abs()
|
(M.splat(getVal(T, i, 63))).abs()
|
||||||
else if (comptime std.mem.eql(u8, op_name, "eq"))
|
else if (comptime std.mem.eql(u8, op_name, "eq"))
|
||||||
(M{ .value = getVal(T, i, 63) }).eq(M{ .value = getVal(T, i +% 3, 63) })
|
(M.splat(getVal(T, i, 63))).eq(M.splat(getVal(T, i +% 3, 63)))
|
||||||
else if (comptime std.mem.eql(u8, op_name, "gt"))
|
else if (comptime std.mem.eql(u8, op_name, "gt"))
|
||||||
(M{ .value = getVal(T, i, 63) }).gt(M{ .value = getVal(T, i +% 3, 63) })
|
(M.splat(getVal(T, i, 63))).gt(M.splat(getVal(T, i +% 3, 63)))
|
||||||
else
|
else
|
||||||
(M{ .value = getVal(T, i, 63) }).mul(3);
|
(M.splat(getVal(T, i, 63))).mul(3);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -223,8 +234,8 @@ fn bench_vsNative(writer: *std.Io.Writer) !void {
|
|||||||
// --- 2. Benchmark Scalar ---
|
// --- 2. Benchmark Scalar ---
|
||||||
const q_start = getTime();
|
const q_start = getTime();
|
||||||
for (0..ITERS) |i| {
|
for (0..ITERS) |i| {
|
||||||
const qa = M{ .value = getValT(T, i) };
|
const qa = M.splat(getValT(T, i));
|
||||||
const qb = if (comptime std.mem.eql(u8, op_name, "div")) S{ .value = getValT(T, 2) } else M{ .value = getValT(T, 2) };
|
const qb = if (comptime std.mem.eql(u8, op_name, "div")) S.splat(getValT(T, 2)) else M.splat(getValT(T, 2));
|
||||||
|
|
||||||
// Scalar logic branch
|
// Scalar logic branch
|
||||||
_ = if (comptime std.mem.eql(u8, op_name, "add"))
|
_ = if (comptime std.mem.eql(u8, op_name, "add"))
|
||||||
@ -338,11 +349,11 @@ fn bench_crossTypeVsNative(writer: *std.Io.Writer) !void {
|
|||||||
// --- 2. Benchmark Scalar ---
|
// --- 2. Benchmark Scalar ---
|
||||||
const q_start = getTime();
|
const q_start = getTime();
|
||||||
for (0..ITERS) |i| {
|
for (0..ITERS) |i| {
|
||||||
const qa = M1{ .value = getValT(T1, i) };
|
const qa = M1.splat(getValT(T1, i));
|
||||||
const qb = if (comptime std.mem.eql(u8, op_name, "div"))
|
const qb = if (comptime std.mem.eql(u8, op_name, "div"))
|
||||||
S2{ .value = getValT(T2, 2) }
|
S2.splat(getValT(T2, 2))
|
||||||
else
|
else
|
||||||
M2{ .value = getValT(T2, 2) };
|
M2.splat(getValT(T2, 2));
|
||||||
|
|
||||||
_ = if (comptime std.mem.eql(u8, op_name, "add"))
|
_ = if (comptime std.mem.eql(u8, op_name, "add"))
|
||||||
qa.add(qb)
|
qa.add(qb)
|
||||||
@ -401,15 +412,15 @@ fn bench_Vector(writer: *std.Io.Writer) !void {
|
|||||||
\\ Vector<N, T> benchmark — {d} iterations, {d} samples/cell
|
\\ Vector<N, T> benchmark — {d} iterations, {d} samples/cell
|
||||||
\\ (Results in ns/op; "---" = not applicable for this length)
|
\\ (Results in ns/op; "---" = not applicable for this length)
|
||||||
\\
|
\\
|
||||||
\\┌──────────────────┬──────┬─────────┬─────────┬─────────┐
|
\\┌──────────────────┬──────┬─────────┬─────────┬─────────┬─────────┬─────────┐
|
||||||
\\│ Operation │ Type │ Len=3 │ Len=4 │ Len=16 │
|
\\│ Operation │ Type │ Len=1 │ Len=3 │ Len=4 │ Len=16 │ Len=100 │
|
||||||
\\├──────────────────┼──────┼─────────┼─────────┼─────────┤
|
\\├──────────────────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤
|
||||||
\\
|
\\
|
||||||
, .{ ITERS, SAMPLES });
|
, .{ ITERS, SAMPLES });
|
||||||
|
|
||||||
const Types = .{ i32, i64, i128, f32, f64 };
|
const Types = .{ i32, i64, i128, f32, f64 };
|
||||||
const TNames = .{ "i32", "i64", "i128", "f32", "f64" };
|
const TNames = .{ "i32", "i64", "i128", "f32", "f64" };
|
||||||
const Lengths = .{ 3, 4, 16 };
|
const Lengths = .{ 1, 3, 4, 16, 100 };
|
||||||
// "cross" is only valid for len=3; other cells will show " --- "
|
// "cross" is only valid for len=3; other cells will show " --- "
|
||||||
const Ops = .{ "add", "div", "mulScalar", "dot", "cross", "product", "pow", "length" };
|
const Ops = .{ "add", "div", "mulScalar", "dot", "cross", "product", "pow", "length" };
|
||||||
|
|
||||||
@ -435,22 +446,22 @@ fn bench_Vector(writer: *std.Io.Writer) !void {
|
|||||||
for (0..SAMPLES) |s_idx| {
|
for (0..SAMPLES) |s_idx| {
|
||||||
const t_start = getTime();
|
const t_start = getTime();
|
||||||
for (0..ITERS) |i| {
|
for (0..ITERS) |i| {
|
||||||
const v1 = V.initDefault(getVal(T, i, 63));
|
const v1 = V.splat(getVal(T, i, 63));
|
||||||
|
|
||||||
if (comptime std.mem.eql(u8, op_name, "add")) {
|
if (comptime std.mem.eql(u8, op_name, "add")) {
|
||||||
const v2 = V.initDefault(getVal(T, i +% 7, 63));
|
const v2 = V.splat(getVal(T, i +% 7, 63));
|
||||||
_ = v1.add(v2);
|
_ = v1.add(v2);
|
||||||
} else if (comptime std.mem.eql(u8, op_name, "div")) {
|
} else if (comptime std.mem.eql(u8, op_name, "div")) {
|
||||||
_ = v1.div(V.initDefault(getVal(T, i +% 2, 63)));
|
_ = v1.div(V.splat(getVal(T, i +% 2, 63)));
|
||||||
} else if (comptime std.mem.eql(u8, op_name, "mulScalar")) {
|
} else if (comptime std.mem.eql(u8, op_name, "mulScalar")) {
|
||||||
const s_val = Q_time{ .value = getVal(T, i +% 2, 63) };
|
const s_val = Q_time.splat(getVal(T, i +% 2, 63));
|
||||||
_ = v1.mulScalar(s_val);
|
_ = v1.mulScalar(s_val);
|
||||||
} else if (comptime std.mem.eql(u8, op_name, "dot")) {
|
} else if (comptime std.mem.eql(u8, op_name, "dot")) {
|
||||||
const v2 = V.initDefault(getVal(T, i +% 5, 63));
|
const v2 = V.splat(getVal(T, i +% 5, 63));
|
||||||
_ = v1.dot(v2);
|
_ = v1.dot(v2);
|
||||||
} else if (comptime std.mem.eql(u8, op_name, "cross")) {
|
} else if (comptime std.mem.eql(u8, op_name, "cross")) {
|
||||||
// len == 3 guaranteed by the guard above
|
// len == 3 guaranteed by the guard above
|
||||||
const v2 = V.initDefault(getVal(T, i +% 5, 63));
|
const v2 = V.splat(getVal(T, i +% 5, 63));
|
||||||
_ = v1.cross(v2);
|
_ = v1.cross(v2);
|
||||||
} else if (comptime std.mem.eql(u8, op_name, "product")) {
|
} else if (comptime std.mem.eql(u8, op_name, "product")) {
|
||||||
_ = v1.product();
|
_ = v1.product();
|
||||||
@ -473,8 +484,67 @@ fn bench_Vector(writer: *std.Io.Writer) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (o_idx < Ops.len - 1) {
|
if (o_idx < Ops.len - 1) {
|
||||||
try writer.print("├──────────────────┼──────┼─────────┼─────────┼─────────┤\n", .{});
|
try writer.print("├──────────────────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try writer.print("└──────────────────┴──────┴─────────┴─────────┴─────────┘\n", .{});
|
try writer.print("└──────────────────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┘\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vectorSIMDvsNative(comptime T: type, writer: *std.Io.Writer) !void {
|
||||||
|
const iterations: u64 = 10_000;
|
||||||
|
const lens = [_]u32{ 1, 2, 3, 4, 5, 10, 100, 1_000, 10_000 };
|
||||||
|
|
||||||
|
try writer.print("\nSIMD Speedup Analysis: {s}\n", .{@typeName(T)});
|
||||||
|
try writer.print("┌────────────┬────────────┬────────────┬────────────┐\n", .{});
|
||||||
|
try writer.print("│ Vector Len │ Scalar (us)│ Vector (us)│ Speedup │\n", .{});
|
||||||
|
try writer.print("├────────────┼────────────┼────────────┼────────────┤\n", .{});
|
||||||
|
|
||||||
|
inline for (lens) |vector_len| {
|
||||||
|
// --- Scalar Test ---
|
||||||
|
var scalar_val: T = 10;
|
||||||
|
const start_scalar = getTime();
|
||||||
|
|
||||||
|
var i: u64 = 0;
|
||||||
|
while (i < iterations * vector_len) : (i += 1) {
|
||||||
|
if (comptime @typeInfo(T) == .int)
|
||||||
|
scalar_val = scalar_val +% 1
|
||||||
|
else
|
||||||
|
scalar_val = scalar_val + 1;
|
||||||
|
}
|
||||||
|
const scalar_time = start_scalar.durationTo(getTime()).toMicroseconds();
|
||||||
|
|
||||||
|
// --- Vector Test ---
|
||||||
|
var vector_val: @Vector(vector_len, T) = @splat(20);
|
||||||
|
const start_vector = getTime();
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
const increment: @Vector(vector_len, T) = @splat(1);
|
||||||
|
while (i < iterations) : (i += 1) {
|
||||||
|
if (comptime @typeInfo(T) == .int)
|
||||||
|
vector_val = vector_val +% increment
|
||||||
|
else
|
||||||
|
vector_val = vector_val + increment;
|
||||||
|
}
|
||||||
|
const vector_time = start_vector.durationTo(getTime()).toMicroseconds();
|
||||||
|
|
||||||
|
// --- Results ---
|
||||||
|
const s_float = @as(f64, @floatFromInt(scalar_time));
|
||||||
|
const v_float = @as(f64, @floatFromInt(vector_time));
|
||||||
|
|
||||||
|
// Speedup = ScalarTime / VectorTime.
|
||||||
|
// > 1.0 means SIMD is faster.
|
||||||
|
const speedup = if (vector_time > 0) s_float / v_float else 0;
|
||||||
|
|
||||||
|
try writer.print("│ {d:<10} │ {d:>10} │ {d:>10} │ {d:>9.2}x │\n", .{
|
||||||
|
vector_len,
|
||||||
|
scalar_time,
|
||||||
|
vector_time,
|
||||||
|
speedup,
|
||||||
|
});
|
||||||
|
try writer.flush();
|
||||||
|
|
||||||
|
std.mem.doNotOptimizeAway(scalar_val);
|
||||||
|
std.mem.doNotOptimizeAway(vector_val);
|
||||||
|
}
|
||||||
|
try writer.print("└────────────┴────────────┴────────────┴────────────┘\n", .{});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,14 +62,13 @@ pub fn finerScales(comptime T1: type, comptime T2: type) Scales {
|
|||||||
// RHS normalisation helpers
|
// RHS normalisation helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const Scalar = @import("Scalar.zig").Scalar;
|
const Quantity = @import("Quantity.zig").Quantity;
|
||||||
|
|
||||||
/// Returns true if `T` is a `Scalar_` type (has `dims`, `scales`, and `value`).
|
/// Returns true if `T` is a `Scalar_` type (has `dims`, `scales`, and `value`).
|
||||||
pub fn isScalarType(comptime T: type) bool {
|
pub fn isScalarType(comptime T: type) bool {
|
||||||
return @typeInfo(T) == .@"struct" and
|
return @typeInfo(T) == .@"struct" and
|
||||||
@hasDecl(T, "dims") and
|
@hasDecl(T, "ISQUANTITY") and
|
||||||
@hasDecl(T, "scales") and
|
@field(T, "ISQUANTITY");
|
||||||
@hasField(T, "value");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the Scalar type that `rhs` will be treated as.
|
/// Resolve the Scalar type that `rhs` will be treated as.
|
||||||
@ -80,19 +79,19 @@ pub fn isScalarType(comptime T: type) bool {
|
|||||||
/// - `BaseT` (the scalar's value type) → dimensionless `Scalar_(BaseT, {}, {})`
|
/// - `BaseT` (the scalar's value type) → dimensionless `Scalar_(BaseT, {}, {})`
|
||||||
///
|
///
|
||||||
/// Everything else is a compile error, including other int/float types.
|
/// Everything else is a compile error, including other int/float types.
|
||||||
pub fn rhsScalarType(comptime BaseT: type, comptime RhsT: type) type {
|
pub fn rhsQuantityType(comptime ValueType: type, N: usize, comptime RhsT: type) type {
|
||||||
if (comptime isScalarType(RhsT)) return RhsT;
|
if (comptime isScalarType(RhsT)) return RhsT;
|
||||||
if (comptime RhsT == comptime_int or RhsT == comptime_float or RhsT == BaseT)
|
if (comptime RhsT == comptime_int or RhsT == comptime_float or RhsT == ValueType)
|
||||||
return Scalar(BaseT, .{}, .{});
|
return Quantity(ValueType, N, .{}, .{});
|
||||||
@compileError(
|
@compileError(
|
||||||
"rhs must be a Scalar, " ++ @typeName(BaseT) ++
|
"rhs must be a Scalar, " ++ @typeName(ValueType) ++
|
||||||
", comptime_int, or comptime_float; got " ++ @typeName(RhsT),
|
", comptime_int, or comptime_float; got " ++ @typeName(RhsT),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert `rhs` to its normalised Scalar form (see `rhsScalarType`).
|
/// Convert `rhs` to its normalised Scalar form (see `rhsScalarType`).
|
||||||
pub inline fn toRhsScalar(comptime BaseT: type, rhs: anytype) rhsScalarType(BaseT, @TypeOf(rhs)) {
|
pub inline fn toRhsQuantity(comptime BaseT: type, N: usize, rhs: anytype) rhsQuantityType(BaseT, N, @TypeOf(rhs)) {
|
||||||
if (comptime isScalarType(@TypeOf(rhs))) return rhs;
|
if (comptime isScalarType(@TypeOf(rhs))) return rhs;
|
||||||
const DimLess = Scalar(BaseT, .{}, .{});
|
const DimLess = Quantity(BaseT, N, .{}, .{});
|
||||||
return DimLess{ .value = @as(BaseT, rhs) };
|
return DimLess{ .data = @splat(@as(BaseT, rhs)) };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Scalar = @import("Scalar.zig").Scalar;
|
pub const Vector = @import("Quantity.zig").Vector;
|
||||||
pub const Vector = @import("Vector.zig").Vector;
|
pub const Scalar = @import("Quantity.zig").Scalar;
|
||||||
pub const Dimensions = @import("Dimensions.zig");
|
pub const Dimensions = @import("Dimensions.zig");
|
||||||
pub const Scales = @import("Scales.zig");
|
pub const Scales = @import("Scales.zig");
|
||||||
pub const Base = @import("Base.zig");
|
pub const Base = @import("Base.zig");
|
||||||
|
|
||||||
test {
|
test {
|
||||||
_ = @import("Scalar.zig");
|
_ = @import("Quantity.zig");
|
||||||
_ = @import("Vector.zig");
|
|
||||||
_ = @import("Dimensions.zig");
|
_ = @import("Dimensions.zig");
|
||||||
_ = @import("Scales.zig");
|
_ = @import("Scales.zig");
|
||||||
_ = @import("Base.zig");
|
_ = @import("Base.zig");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user