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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
that uses `Dual` and `HyperDual` directly (no derivator). @rtmongold (#106)
- Fixed `examples/README.md` table entry for `optimization_solvers` (was an orphan
bullet). @rtmongold (#117)
- **`Primal` scalar projection.** New `to_f64` / `to_f32` trait for edge use (viz, logging, etc.), kept separate from
`Numeric`. `showcase/viz` now uses it instead of its private `Plottable` trait. @ProtoFN (#131)
- Property tests for linear/quadratic approximation invariants (exactness on
matching-degree polynomials, metrics consistency). @rtmongold (#130)
- Property tests that autodiff and central finite-difference derivatives agree on random
Expand Down
2 changes: 2 additions & 0 deletions crates/multicalc/src/scalar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ pub mod function;
pub mod hyper_dual;
pub mod jet;
pub mod numeric;
pub mod primal;
Comment thread
rtmongold marked this conversation as resolved.

pub use dual::Dual;
pub use function::{ScalarFn, ScalarFnN, VectorFn, c};
pub use hyper_dual::HyperDual;
pub use jet::Jet;
pub use numeric::Numeric;
pub use primal::Primal;
72 changes: 72 additions & 0 deletions crates/multicalc/src/scalar/primal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! Edge-only projection to `f64` / `f32` (the reverse of [`Numeric::from_f64`]).
//!
//! Kept separate from [`Numeric`] so generic algorithms cannot strip autodiff.
//! Autodiff types return their value part; nesting such as `Dual<HyperDual<f64>>` works.

use crate::{Dual, HyperDual, Jet, Numeric};

/// A scalar that can be projected to `f64` and `f32`.
Comment thread
rtmongold marked this conversation as resolved.
///
/// ```
/// use multicalc::scalar::{Dual, Primal};
///
/// let x = Dual::new(2.0, 99.0);
/// assert_eq!(x.to_f64(), 2.0);
/// ```
pub trait Primal {
/// Returns the value as an `f64` (the primal part, for autodiff scalars).
fn to_f64(&self) -> f64;

/// Returns the value as an `f32` (the primal part, for autodiff scalars).
fn to_f32(&self) -> f32;
}

impl Primal for f64 {
fn to_f64(&self) -> f64 {
*self
}

fn to_f32(&self) -> f32 {
*self as f32
}
}

impl Primal for f32 {
fn to_f64(&self) -> f64 {
*self as f64
}

fn to_f32(&self) -> f32 {
*self
}
}

impl<T: Numeric + Primal> Primal for Dual<T> {
fn to_f64(&self) -> f64 {
self.value.to_f64()
}

fn to_f32(&self) -> f32 {
self.value.to_f32()
}
}

impl<T: Numeric + Primal> Primal for HyperDual<T> {
fn to_f64(&self) -> f64 {
self.real.to_f64()
}

fn to_f32(&self) -> f32 {
self.real.to_f32()
}
}

impl<T: Numeric + Primal, const N: usize> Primal for Jet<T, N> {
fn to_f64(&self) -> f64 {
self.value().to_f64()
}

fn to_f32(&self) -> f32 {
self.value().to_f32()
}
}
22 changes: 22 additions & 0 deletions crates/multicalc/tests/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,28 @@ mod numeric_methods {
}
}

mod primal {
use multicalc::scalar::Primal;
use multicalc::{Dual, HyperDual, Jet};

#[test]
fn projects_floats_and_autodiff_primals() {
assert_eq!(2.0_f64.to_f64(), 2.0);
assert_eq!(2.0_f32.to_f64(), 2.0);

assert_eq!(Dual::new(2.0, 99.0).to_f64(), 2.0);
assert_eq!(HyperDual::new(2.0, 1.0, 1.0, 1.0).to_f64(), 2.0);
assert_eq!(Jet::<f64, 2>::variable(2.0).to_f64(), 2.0);

let nested = Dual::new(
HyperDual::new(2.0, 1.0, 1.0, 1.0),
HyperDual::new(99.0, 3.0, 3.0, 3.0),
);
assert_eq!(nested.to_f64(), 2.0);
assert_eq!(nested.to_f32(), 2.0);
}
}

mod dual {
use multicalc::scalar::Dual;
use multicalc::scalar::Numeric;
Expand Down
44 changes: 0 additions & 44 deletions showcase/viz/src/convert.rs

This file was deleted.

3 changes: 1 addition & 2 deletions showcase/viz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
//! ([`RerunSink`], live or recorded) and a CSV backend ([`CsvSink`]) for the `plot.py` fallback.
//! A satellite crate: never a dependency of the core, excluded from bare-metal builds.

mod convert;
mod csv_sink;
mod rerun_sink;
mod sink;

#[doc(hidden)]
pub mod loop_util;

pub use convert::Plottable;
pub use csv_sink::CsvSink;
pub use multicalc::scalar::Primal;
pub use rerun_sink::RerunSink;
pub use sink::{Rgba, VizError, VizSink, VizSinkExt};

Expand Down
11 changes: 6 additions & 5 deletions showcase/viz/src/sink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
//!
//! [`VizSink`] is kept object-safe so callers can hold a `&mut dyn VizSink` and swap backends at
//! runtime; that is why `scalar` takes a plain `f64`. The generic convenience form that accepts
//! any [`Plottable`] scalar lives on the blanket [`VizSinkExt`].
//! any [`Primal`] scalar lives on the blanket [`VizSinkExt`].

use multicalc::scalar::Primal;

use crate::convert::Plottable;
use core::fmt;

/// An error from a sink backend.
Expand Down Expand Up @@ -136,9 +137,9 @@ pub trait VizSink {

/// Convenience extensions kept off the object-safe [`VizSink`].
pub trait VizSinkExt: VizSink {
/// Logs any [`Plottable`] scalar without an explicit `to_plot_f64`.
fn scalar_of(&mut self, path: &str, value: impl Plottable) -> Result<(), VizError> {
self.scalar(path, value.to_plot_f64())
/// Logs any [`Primal`] scalar without an explicit `to_f64`.
fn scalar_of(&mut self, path: &str, value: impl Primal) -> Result<(), VizError> {
Comment thread
ProtoFN marked this conversation as resolved.
self.scalar(path, value.to_f64())
}
}

Expand Down
Loading