Skip to main content

adam_rs/
lib.rs

1//! # adam-rs
2//!
3//! A library for constructing and executing property model constraint graphs.
4//!
5//! A property model is a bipartite graph of **value cells** and **relationships**.
6//! Cells hold type-erased values. Relationships define multi-way constraints: each
7//! relationship supplies multiple methods, and at propagation time the planner
8//! selects one method per relationship based on cell write-recency (strength),
9//! then executes the selected methods in dependency order.
10//!
11//! # Callback purity
12//!
13//! Every [`Method`], [`Filter`], [`MatchExpr`], and [`Requirement`] callback must be
14//! purely functional: its result depends only on its explicit input values and
15//! immutable captured constants. Equal input values must produce equivalent output
16//! values or errors. This also applies to range-filter bound evaluators and
17//! conditional equality functions.
18//!
19//! Callbacks must not mutate inputs or captured state, perform I/O, or read mutable
20//! external state such as clocks, random generators, or independently changing shared
21//! data. Represent changing dependencies as input cells instead.
22//!
23//! There is no guarantee that a callback will execute, or when or how often it will
24//! execute. The runtime may omit evaluation, reuse a previously computed result, or
25//! reevaluate a callback while preserving the sheet's value and diagnostic semantics.
26//! Observe propagation through cell values and diagnostics, not callback side effects.
27//! Rust's `Fn` bound does not enforce purity; this is a caller obligation.
28//!
29//! # Example
30//!
31//! ```rust
32//! use adam_rs::{Sheet, Method};
33//!
34//! let mut sheet = Sheet::new();
35//! let a = sheet.add_cell(2.0_f64);
36//! let b = sheet.add_cell(3.0_f64);
37//! let c = sheet.add_cell(0.0_f64);
38//!
39//! // Three methods encoding a × b = c in each direction.
40//! let methods = vec![
41//!     Method::from_fn_2_1([a, b], c, |x: &f64, y: &f64| Ok((*x) * (*y))),
42//!     Method::from_fn_2_1([b, c], a, |x: &f64, y: &f64| Ok((*y) / (*x))),
43//!     Method::from_fn_2_1([a, c], b, |x: &f64, y: &f64| Ok((*y) / (*x))),
44//! ];
45//! sheet.add_relationship(methods).unwrap();
46//!
47//! sheet.write(a, 2.0_f64).unwrap();
48//! sheet.write(b, 3.0_f64).unwrap();
49//! sheet.propagate().unwrap();
50//!
51//! assert_eq!(*sheet.read::<f64>(c).unwrap(), 6.0);
52//! ```
53//!
54//! # Out cells and requirements
55//!
56//! An out cell is always derived by exactly one fixed writer method, with optional
57//! requirements checked after every `propagate()`. Display labels belong to the caller,
58//! not the sheet. Unlike an ordinary derived cell, an
59//! out cell can never be `write()`-ed or claimed as another method's output — but it
60//! remains an ordinary, freely-referenceable cell everywhere else (as another
61//! relationship's input, a conditional's match subject, and so on).
62//!
63//! ```rust
64//! use adam_rs::{Requirement, Method, Sheet};
65//!
66//! let mut sheet = Sheet::new();
67//! let width = sheet.add_cell(0_i32);
68//! let height = sheet.add_cell(0_i32);
69//! let max_area = sheet.add_cell(100_i32);
70//! let area = sheet.add_cell(0_i32);
71//!
72//! let writer = Method::from_fn_2_1([width, height], area, |w: &i32, h: &i32| {
73//!     w.checked_mul(*h).ok_or_else(|| anyhow::anyhow!("overflow"))
74//! });
75//! let area_cell = sheet
76//!     .add_out(
77//!         writer,
78//!         vec![Requirement::from_fn_2([area, max_area], |a: &i32, max: &i32| Ok(a <= max))],
79//!     )
80//!     .unwrap();
81//!
82//! sheet.write(width, 20_i32).unwrap();
83//! sheet.write(height, 3_i32).unwrap();
84//! sheet.propagate().unwrap();
85//! assert!(sheet.cell_requirements_valid(area_cell));
86//!
87//! sheet.write(height, 30_i32).unwrap();
88//! sheet.propagate().unwrap();
89//! assert!(!sheet.cell_requirements_valid(area_cell));
90//! ```
91//!
92//! # Filters
93//!
94//! A filter conforms or rejects a value written externally to its cell — applied
95//! live by `propagate()` against the cell's own current value, not synchronously by
96//! `write()`. It's also re-checked, as a non-gating diagnostic only, against a value
97//! a relationship's method derives for that cell — a derived value is never
98//! corrected, only flagged.
99//!
100//! ```rust
101//! use adam_rs::{Filter, Method, Sheet};
102//!
103//! let mut sheet = Sheet::new();
104//! let a = sheet.add_cell(0_i32);
105//! let b = sheet.add_cell(0_i32);
106//! sheet
107//!     .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
108//!     .unwrap();
109//! sheet
110//!     .add_filter(b, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
111//!     .unwrap();
112//! sheet
113//!     .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
114//!     .unwrap();
115//!
116//! // write() leaves the raw value in source; it is conformed by propagate()...
117//! sheet.write(a, 500_i32).unwrap();
118//! assert_eq!(*sheet.read::<i32>(a).unwrap(), 500);
119//!
120//! sheet.propagate().unwrap();
121//! assert_eq!(*sheet.read::<i32>(a).unwrap(), 100);
122//!
123//! // A derived value that would fail the same filter is only diagnosed, never
124//! // corrected: `b` doubles `a`'s now-conformed value, still exceeding the filter's range.
125//! assert_eq!(*sheet.read::<i32>(b).unwrap(), 200);
126//! assert!(sheet.filter_violated_cells().any(|id| id == b));
127//! ```
128//!
129//! # Invariants
130//!
131//! Enforced by code:
132//!
133//! - Every relationship's methods share the same `inputs ∪ outputs` cell set
134//!   ([`Sheet::add_relationship`] validation; [`Error::MismatchedMethodCells`]).
135//! - No two relationships may claim the same cell as an output in one round —
136//!   self-referencing outputs are claimed exactly like any other
137//!   ([`Error::Conflict`] when infeasible).
138//! - The selected methods' induced dependency digraph is acyclic before execution
139//!   ([`Error::Cycle`] when not).
140//! - A self-referencing input never reads a same-round derived value. Instead it reads a
141//!   seed reconstructed from `source`: `planner::evaluate_seeds` folds every other
142//!   relationship incident to the cell (excluding its own claimant) through that
143//!   relationship's cell-producing method, giving the cell's aspiration before its
144//!   claimant tightens it; a cell no other relationship contributes to just reads its
145//!   own `source`. The seed is rebuilt from `source` every round, so it is never stale
146//!   and never accumulates.
147//! - `source` is written only by [`Sheet::write`]/[`Sheet::add_cell`], never by method
148//!   or filter execution.
149//! - An `Out`-kind cell can never be [`Sheet::write`]-ed or claimed as another method's
150//!   output ([`Error::InvalidCellKind`]).
151//!
152//! Enforced only by convention (caller contract, not checked by the runtime):
153//!
154//! - All method, filter, conditional-expression, and requirement callbacks satisfy
155//!   the callback-purity contract above, with no invocation guarantee.
156//! - A self-referencing method must be idempotent: applying it twice to the same inputs
157//!   must produce the same result as applying it once.
158//! - A filter must be idempotent and conform its cell's value using its argument
159//!   cells' values.
160//! - Iteration order used to break ties among equal-strength cells is not stable API
161//!   and must not be relied on by callers.
162//!
163//! The source-capture property:
164//!
165//! Capturing every cell's `source` value and reapplying the highest-strength sources
166//! reconstructs the sheet's *current* state, but not what a subsequent edit will do,
167//! since no method-selection state is captured, only values. This holds even for a
168//! self-referencing cell: `planner::evaluate_seeds` reconstructs its contribution to a
169//! sibling relationship's method purely from current `source` values (recursively, for
170//! any other self-referencing cell that sibling method itself reads), so no state
171//! beyond `source` needs to be captured to reproduce the derived state.
172
173pub mod cell;
174pub mod conditional;
175pub mod error;
176pub mod filter;
177mod planner;
178pub mod relationship;
179pub mod requirement;
180pub mod sheet;
181
182pub use cell::{CellId, CellKind};
183pub use conditional::{ConditionalId, MatchExpr};
184pub use error::{Error, ErrorSite};
185pub use filter::{Filter, FilterKind, FilterViolation};
186pub use relationship::{Method, RelationshipId};
187pub use requirement::{Requirement, RequirementId};
188pub use sheet::Sheet;