Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.8.0 - 2026-06-16]

### Added
- New `affn::interpolation` module providing domain-agnostic interpolation primitives for typed affine geometry.
- `CubicHermiteTable<T>` for cubic Hermite interpolation over any `HermiteInterpolable` type (including `Position`, `Vector`, `Direction`, and `XYZ`).
- `ScalarCubicHermiteTable` for efficient scalar-only interpolation.
- `HermiteNode<T>` and `HermiteTableEvaluation<T>` for representing nodes and evaluation results in Hermite interpolation.
- `InterpolationAbscissa` and `AbscissaDelta` for typed abscissa (e.g., time) with unit support via `qtty`.
- `InterpolationError` for comprehensive error handling during interpolation operations (out-of-bounds, insufficient nodes, etc.).
- Trait-based interpolation support via `HermiteInterpolable` (types that can be interpolated) and `HermiteBasis` (basis function implementations).
- `HermiteTableEvaluation::try_value()` for fallible evaluation with proper error propagation.

### Changed
- Interpolation module is now domain-agnostic and focuses on scalar abscissae with strongly-typed values, enabling downstream crates to implement astronomy-specific epoch handling.

## [0.7.3 - 2026-05-25]

### Changed
Expand Down
77 changes: 38 additions & 39 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[package]
name = "affn"
version = "0.7.3"
version = "0.8.0"
edition = "2021"
authors = ["VPRamon <vallespuigramon@gmail.com>"]
description = "Affine geometry primitives: strongly-typed coordinate systems, reference frames, and centers for scientific computing."
Expand All @@ -21,7 +21,7 @@ serde = ["dep:serde", "qtty/serde"]
astro = ["qtty/astro"]

[dependencies]
affn-derive = { version = "0.7", path = "affn-derive" }
affn-derive = { version = "0.8", path = "affn-derive" }
qtty = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }

Expand Down
2 changes: 1 addition & 1 deletion affn-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "affn-derive"
version = "0.7.3"
version = "0.8.0"
edition = "2021"
authors = ["VPRamon <vallespuigramon@gmail.com>"]
description = "Derive macros for affn: ReferenceFrame and ReferenceCenter"
Expand Down
2 changes: 1 addition & 1 deletion src/cartesian/direction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ use std::ops::Mul;
/// # Invariants
///
/// All public constructors ensure the direction is normalized. For unchecked
/// construction, use [`from_xyz_unchecked`](Self::from_xyz_unchecked).
/// construction, use the crate-internal unchecked constructor.
///
/// # Zero-Cost Abstraction
///
Expand Down
6 changes: 6 additions & 0 deletions src/cartesian/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ impl<C: ReferenceCenter, F: ReferenceFrame, U: LengthUnit> Position<C, F, U> {
self.xyz.z()
}

/// Returns whether all position components are finite.
#[inline]
pub fn is_finite(&self) -> bool {
self.x().value().is_finite() && self.y().value().is_finite() && self.z().value().is_finite()
}

/// Returns a reference to the underlying `[Quantity<U>; 3]` array.
#[inline]
pub fn as_array(&self) -> &[Quantity<U>; 3] {
Expand Down
48 changes: 44 additions & 4 deletions src/cartesian/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ use super::xyz::XYZ;
use crate::frames::ReferenceFrame;
use qtty::dimensionless::Ratio;
use qtty::length::LengthUnit;
use qtty::{Quantity, Unit, UnitMul};
use qtty::{Quantity, Unit, UnitDiv, UnitMul};

use std::marker::PhantomData;
use std::ops::{Add, Div, Neg, Sub};
use std::ops::{Add, Div, Mul, Neg, Sub};

/// A free vector in 3D Cartesian coordinates.
///
Expand Down Expand Up @@ -199,6 +199,29 @@ impl<F: ReferenceFrame, U: Unit> Vector<F, U> {
pub fn negate(&self) -> Self {
Self::from_xyz(-self.xyz)
}

/// Returns whether all vector components are finite.
#[inline]
pub fn is_finite(&self) -> bool {
self.x().value().is_finite() && self.y().value().is_finite() && self.z().value().is_finite()
}

/// Divides this vector by a typed quantity, carrying the resulting unit in
/// the output vector.
///
/// This is the dimensional counterpart to [`scale`](Self::scale). It is an
/// inherent method rather than a `/` operator so it can coexist with the
/// established same-unit division operator that returns a dimensionless
/// ratio vector.
#[inline]
pub fn div_quantity<X>(&self, rhs: Quantity<X>) -> Vector<F, <U as UnitDiv<X>>::Output>
where
X: Unit,
U: UnitDiv<X>,
<U as UnitDiv<X>>::Output: Unit,
{
Vector::<F, <U as UnitDiv<X>>::Output>::new(self.x() / rhs, self.y() / rhs, self.z() / rhs)
}
}

// =============================================================================
Expand Down Expand Up @@ -321,14 +344,31 @@ impl<F: ReferenceFrame, U: Unit> Neg for Vector<F, U> {
forward_ref_unop! { impl[F: ReferenceFrame, U: Unit] Neg, neg for Vector<F, U> }

// =============================================================================
// Scalar Division: Vector<F, U> / Quantity<U> → Vector<F, Ratio>
// Dimensional Operations: Vector<F, U> * Quantity<X> and / Quantity<X>
// =============================================================================

impl<F, U, X> Mul<Quantity<X>> for Vector<F, U>
where
F: ReferenceFrame,
U: Unit + UnitMul<X>,
X: Unit,
{
type Output = Vector<F, <U as UnitMul<X>>::Output>;

/// Multiplies every component by a typed quantity.
#[inline]
fn mul(self, rhs: Quantity<X>) -> Self::Output {
Vector::<F, <U as UnitMul<X>>::Output>::new(self.x() * rhs, self.y() * rhs, self.z() * rhs)
}
}

forward_ref_binop! { impl[F: ReferenceFrame, U: Unit + UnitMul<X>, X: Unit] Mul, mul for Vector<F, U>, Quantity<X> }

impl<F: ReferenceFrame, U: Unit> Div<Quantity<U>> for Vector<F, U> {
type Output = Vector<F, Ratio>;

/// Divides each component by a scalar with the same unit, producing a
/// dimensionless velocity vector (e.g. β = v/c for aberration).
/// dimensionless vector (e.g. beta = v/c for aberration).
#[inline]
fn div(self, rhs: Quantity<U>) -> Vector<F, Ratio> {
let c = rhs.value();
Expand Down
Loading
Loading