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
//! `labeled` — string-keyed wrappers over the existing AD modes.
//!
//! This module provides name-indexed wrappers around the forward AD modes
//! in the crate ([`FReal`](crate::freal::FReal), [`Dual`](crate::dual::Dual),
//! [`Dual2`](crate::dual2::Dual2)), trading a small ownership overhead
//! (one [`Arc<VarRegistry>`](std::sync::Arc) per wrapped value) for
//! ergonomic gradient readback via string labels instead of positional
//! indices. The wrappers delegate **every** arithmetic operation to the
//! underlying positional type — all derivative values are bit-identical
//! to the positional equivalents, and Criterion benchmarks (Phase 2 of
//! v0.2.0) gate the labeled layer at ≤5% runtime overhead vs positional.
//!
//! # Available with `labeled` feature
//!
//! This entire module is gated behind the `labeled` feature. The default
//! build does not link `indexmap` and exposes none of these types.
//!
//! # Feature matrix
//!
//! - `labeled` — enables [`VarRegistry`], [`LabeledFReal`], [`LabeledDual`],
//! [`LabeledDual2`].
//! - `labeled-ndarray` — transitively enables `labeled` and adds
//! `LabeledJacobian` with `ndarray::Array2<f64>` outputs.
//! - `labeled-hessian` — transitively enables `labeled` and `dual2-vec`;
//! adds `LabeledHessian` and `compute_full_hessian` (Phase 4 integration).
//!
//! # The two-phase forward-mode contract
//!
//! Forward-mode labeled wrappers (`LabeledFReal<T>`, `LabeledDual`,
//! `LabeledDual2<f64>`) are constructed via [`LabeledForwardTape`], the
//! forward-mode parallel of [`LabeledTape`] for reverse mode. The tape
//! owns the [`VarRegistry`] and hands out wrappers through one of two
//! shapes — see [`forward_tape`] for the full design rationale. The
//! canonical usage pattern for `LabeledDual` is:
//!
//! ```
//! use xad_rs::labeled::{LabeledForwardScope, LabeledForwardTape};
//!
//! // 1. Build a tape and declare inputs. `declare_dual` registers the
//! // name in the pending registry and returns an opaque handle.
//! let mut ft = LabeledForwardTape::new();
//! let spot_h = ft.declare_dual("spot", 100.0);
//! let strike_h = ft.declare_dual("strike", 105.0);
//! let _vol_h = ft.declare_dual("vol", 0.2);
//!
//! // 2. Freeze the tape. This consumes the tape, allocates the final
//! // `Arc<VarRegistry>`, stamps the TLS active-registry slot, and
//! // returns a scope object holding fully-seeded wrappers.
//! let scope: LabeledForwardScope = ft.freeze_dual();
//!
//! // 3. Retrieve wrappers via the scope's handle lookup. The returned
//! // reference is borrowed from the scope and stays valid for the
//! // scope's lifetime.
//! let spot = scope.dual(spot_h);
//! let strike = scope.dual(strike_h);
//!
//! // 4. Compute. All operator impls go through Shape A — no per-op Arc
//! // clone, no per-op registry lookup.
//! let moneyness = spot / strike;
//!
//! // 5. Read the gradient by name. The accessor reads the active
//! // registry off the TLS slot set up by `freeze_dual`.
//! assert!((moneyness.partial("spot") - (1.0 / 105.0)).abs() < 1e-14);
//! assert!((moneyness.partial("strike") - (-100.0 / (105.0 * 105.0))).abs() < 1e-14);
//! assert_eq!(moneyness.partial("vol"), 0.0); // vol doesn't appear in moneyness
//! ```
//!
//! Once a [`VarRegistry`] has been frozen by a tape, it cannot be
//! mutated: there is no API to add names post-freeze, and user code
//! must not reach through `Arc::get_mut` to bypass this contract.
//!
//! # Cross-registry operations panic (debug-only)
//!
//! Every operator (`+`, `-`, `*`, `/`, unary `-`) between two labeled
//! forward-mode wrappers checks that both operands were constructed
//! under the **same** `LabeledForwardTape` scope, using a TLS generation
//! counter stamped at construction time (see
//! [`forward_tape::check_gen`](crate::labeled::forward_tape) and the
//! `D-02` decision block in `.planning/phases/02.2-*`). The check is
//! gated on `#[cfg(debug_assertions)]`; release builds pay zero per-op
//! cost. Mixing values from two different tapes in a debug build panics
//! with a message like:
//!
//! ```text
//! xad_rs::labeled: cross-registry forward-mode op detected
//! (lhs tape generation = 42, rhs tape generation = 43).
//! Both operands must come from the same LabeledForwardTape scope.
//! ```
//!
//! # Zero-overhead wrapper layout (Shape A)
//!
//! Forward-mode labeled wrappers are **Shape A**: the per-value struct
//! holds only the positional inner value. Three consequences:
//!
//! 1. **No lifetime parameters leak to user code.** Labeled values are
//! `'static`, so they can be stored in structs, returned from
//! functions, and passed between threads (for non-`!Send`-marked
//! types) without lifetime annotations. The tape/scope is `!Send`;
//! the per-value wrappers are not.
//! 2. **Zero per-op refcount cost.** Binary operators delegate directly
//! to the positional inner type's impl — no `Arc::clone`, no
//! `VarRegistry` lookup, no allocation.
//! 3. **`Clone` on a labeled wrapper is exactly the inner clone.** For
//! `Copy` positional types (`Dual2<T>`) the labeled wrapper is
//! `Clone` but not `Copy` (we require `Clone` derive since inner
//! `Dual` has a `Vec<f64>` gradient).
//!
//! # Supported modes
//!
//! - [`LabeledFReal<T>`] — labeled single-direction forward-mode.
//! - [`LabeledDual`] — labeled multi-variable forward-mode (f64-only).
//! - [`LabeledDual2<T>`] — labeled seeded second-order forward-mode.
//! - [`LabeledAReal`] + [`LabeledTape`] — labeled reverse-mode (Phase 2).
//! See [`areal`] for the two-phase `input()` -> `freeze()` -> `gradient()`
//! contract and the `!Send` thread-local discipline.
//!
//! With the `labeled-ndarray` sub-feature: `LabeledJacobian` +
//! `jacobian::compute_labeled_jacobian` for `Array2<f64>`-valued labeled
//! Jacobians.
pub use ;
pub use LabeledDual;
pub use LabeledDual2;
pub use ;
pub use LabeledFReal;
pub use VarRegistry;
pub use ;
pub use ;