1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//! [`Dual<F>`] — forward-mode dual-number scalar field for automatic differentiation.
//!
//! `Dual(a, b) = a + ε·b`, `ε² = 0` (nilpotent; NOT complex).
//! Seeding a parameter tangent with 1.0 propagates its exact derivative
//! through every Chernoff kernel at zero new heap allocation (§46, ADR-0133).
//!
//! Arithmetic rules (§46.2, NORMATIVE):
//! ```text
//! U + V = (u+v, u'+v') (linearity)
//! U · V = (uv, u'v + uv') (product rule)
//! U / V = (u/v, (u'v − uv')/v²) (quotient rule)
//! g(U) = (g(u), g'(u)·u') (chain rule — all transcendentals)
//! ```
//! `PartialOrd` compares value only; tangent is carried, never compared,
//! so `tau < 0` / `a(x) < 0` validation guards behave identically to scalar.
//!
//! `num_traits::Float` chain-rule ops are in `dual_helpers.rs` (included below).
use core::{
fmt,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
};
use num_traits::{Float, Num, NumCast, One, ToPrimitive, Zero};
use crate::float::SemiflowFloat;
// ── Struct ───────────────────────────────────────────────────────────────────
/// Dual number `value + ε·tangent` over base float field `F` (§46.1, ADR-0133).
///
/// Implements [`SemiflowFloat`]: every generic kernel at `F = Dual<f64>` gains
/// forward-mode AD at zero allocation. `Dual<Dual<f64>>` gives second
/// derivatives (§46.4).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Dual<F: SemiflowFloat = f64> {
/// Primal value component.
pub value: F,
/// Tangent (derivative) component.
pub tangent: F,
}
impl<F: SemiflowFloat> Dual<F> {
/// Explicit `(value, tangent)` pair.
#[inline]
pub const fn new(value: F, tangent: F) -> Self {
Self { value, tangent }
}
/// θ-independent constant: tangent = 0.
#[inline]
pub fn constant(value: F) -> Self {
Self {
value,
tangent: F::zero(),
}
}
/// Seeded parameter θ: tangent = 1 (forward sweep seeds here).
#[inline]
pub fn variable(value: F) -> Self {
Self {
value,
tangent: F::one(),
}
}
}
impl<F: SemiflowFloat + fmt::Display> fmt::Display for Dual<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}+ε·{}", self.value, self.tangent)
}
}
// ── Arithmetic — §46.2 (NORMATIVE) ──────────────────────────────────────────
impl<F: SemiflowFloat> Add for Dual<F> {
type Output = Self;
#[inline]
fn add(self, r: Self) -> Self {
Self::new(self.value + r.value, self.tangent + r.tangent)
}
}
impl<F: SemiflowFloat> Sub for Dual<F> {
type Output = Self;
#[inline]
fn sub(self, r: Self) -> Self {
Self::new(self.value - r.value, self.tangent - r.tangent)
}
}
impl<F: SemiflowFloat> Mul for Dual<F> {
type Output = Self;
/// §46.2 product rule: `(uv, u'v + uv')`.
#[inline]
fn mul(self, r: Self) -> Self {
Self::new(
self.value * r.value,
self.tangent * r.value + self.value * r.tangent,
)
}
}
impl<F: SemiflowFloat> Div for Dual<F> {
type Output = Self;
/// §46.2 quotient rule: `(u/v, (u'v − uv')/v²)`.
#[inline]
fn div(self, r: Self) -> Self {
Self::new(
self.value / r.value,
(self.tangent * r.value - self.value * r.tangent) / (r.value * r.value),
)
}
}
impl<F: SemiflowFloat> Rem for Dual<F> {
type Output = Self;
/// Remainder: value only; tangent = 0 (piecewise-constant, §46.2).
#[inline]
fn rem(self, r: Self) -> Self {
Self::new(self.value % r.value, F::zero())
}
}
impl<F: SemiflowFloat> Neg for Dual<F> {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self::new(-self.value, -self.tangent)
}
}
impl<F: SemiflowFloat> AddAssign for Dual<F> {
#[inline]
fn add_assign(&mut self, r: Self) {
*self = *self + r;
}
}
impl<F: SemiflowFloat> SubAssign for Dual<F> {
#[inline]
fn sub_assign(&mut self, r: Self) {
*self = *self - r;
}
}
impl<F: SemiflowFloat> MulAssign for Dual<F> {
#[inline]
fn mul_assign(&mut self, r: Self) {
*self = *self * r;
}
}
impl<F: SemiflowFloat> DivAssign for Dual<F> {
#[inline]
fn div_assign(&mut self, r: Self) {
*self = *self / r;
}
}
impl<F: SemiflowFloat> RemAssign for Dual<F> {
#[inline]
fn rem_assign(&mut self, r: Self) {
*self = *self % r;
}
}
// ── PartialOrd — value only (§46.2) ─────────────────────────────────────────
impl<F: SemiflowFloat> PartialOrd for Dual<F> {
/// §46.2: ordering compares **value only**; tangent is never compared.
#[inline]
fn partial_cmp(&self, o: &Self) -> Option<core::cmp::Ordering> {
self.value.partial_cmp(&o.value)
}
}
// ── num_traits: Zero / One / ToPrimitive / NumCast / Num ────────────────────
impl<F: SemiflowFloat> Zero for Dual<F> {
#[inline]
fn zero() -> Self {
Self::constant(F::zero())
}
#[inline]
fn is_zero(&self) -> bool {
self.value.is_zero()
}
}
impl<F: SemiflowFloat> One for Dual<F> {
#[inline]
fn one() -> Self {
Self::constant(F::one())
}
}
impl<F: SemiflowFloat> ToPrimitive for Dual<F> {
#[inline]
fn to_i64(&self) -> Option<i64> {
self.value.to_i64()
}
#[inline]
fn to_u64(&self) -> Option<u64> {
self.value.to_u64()
}
#[inline]
fn to_f64(&self) -> Option<f64> {
self.value.to_f64()
}
#[inline]
fn to_f32(&self) -> Option<f32> {
self.value.to_f32()
}
}
impl<F: SemiflowFloat> NumCast for Dual<F> {
#[inline]
fn from<T: ToPrimitive>(n: T) -> Option<Self> {
F::from(n).map(Self::constant)
}
}
impl<F: SemiflowFloat> Num for Dual<F> {
type FromStrRadixErr = <F as Num>::FromStrRadixErr;
fn from_str_radix(s: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
F::from_str_radix(s, radix).map(Self::constant)
}
}
// ── num_traits::Float — §46.2 chain-rule ops (NORMATIVE) ────────────────────
// Moved to dual_helpers.rs (batch H4 suckless split) and included below.
include!("dual_helpers.rs");
// ── SemiflowFloat impl (§46, ADR-0133) ────────────────────────────────────────
/// Registers `Dual<F>` as a first-class member of the `SemiflowFloat` family.
///
/// This single `impl` makes every generic kernel accept `F = Dual<f64>`;
/// no per-kernel `ChernoffFunction` impl is needed (§46, ADR-0133).
impl<F: SemiflowFloat> SemiflowFloat for Dual<F> {}
// ── Tests ────────────────────────────────────────────────────────────────────
#[cfg(test)]
// Exact float comparisons in tests verify round-trip identity or sentinel values.
#[allow(clippy::float_cmp)]
mod tests {
include!("dual_tests.rs");
}