pub struct Sheet { /* private fields */ }Expand description
Owns a complete property model constraint graph.
Create cells with Sheet::add_cell, define multi-way constraints with
Sheet::add_relationship, write input values with Sheet::write,
then call Sheet::propagate to update derived cells. Propagation automatically
reuses eligible prepared plans or plans again when necessary.
After a mutating operation returns an error, the sheet’s state is unspecified. Do not inspect or reuse it; discard it and rebuild from host-owned inputs.
§Example
use adam_rs::{Sheet, Method};
let mut sheet = Sheet::new();
let a = sheet.add_cell(0_i32);
let b = sheet.add_cell(0_i32);
sheet.add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))]).unwrap();
sheet.write(a, 3_i32).unwrap();
assert_eq!(*sheet.read::<i32>(a).unwrap(), 3);Implementations§
Source§impl Sheet
impl Sheet
Sourcepub fn add_cell<T: Any + PartialEq + 'static>(&mut self, value: T) -> CellId
pub fn add_cell<T: Any + PartialEq + 'static>(&mut self, value: T) -> CellId
Registers a cell with an initial value and returns a stable handle.
The cell’s TypeId is fixed at creation time; subsequent write and
read calls that use a different type will return Error::TypeMismatch.
Each call increments the sheet’s internal strength counter and sets bit 63 of the result. This partitions the strength space: written/added cells always have higher strength than derived cells, ensuring stability across conditional branch switches.
- Complexity: Amortized O(1), plus O(A) when invalidation releases the final owners of prepared artifacts of total size A.
§Examples
See the graph example.
Sourcepub fn add_source<T: Any + PartialEq + 'static>(&mut self, value: T) -> CellId
pub fn add_source<T: Any + PartialEq + 'static>(&mut self, value: T) -> CellId
Registers a cell that can never be claimed as any method’s output — always a planner source, forever.
- Complexity: Amortized O(1), plus O(A) when invalidation releases the final owners of prepared artifacts of total size A.
§Examples
See the graph example.
Sourcepub fn cell_type_id(&self, id: CellId) -> Option<TypeId>
pub fn cell_type_id(&self, id: CellId) -> Option<TypeId>
Returns the fixed concrete type of a live cell, or None for an invalid ID.
§Examples
use std::any::TypeId;
use adam_rs::{CellId, Sheet};
let mut sheet = Sheet::new();
let cell = sheet.add_cell(7i32);
assert_eq!(sheet.cell_type_id(cell), Some(TypeId::of::<i32>()));
assert_eq!(sheet.cell_type_id(CellId::default()), None);Sourcepub fn cell_kind(&self, id: CellId) -> Option<CellKind>
pub fn cell_kind(&self, id: CellId) -> Option<CellKind>
Returns id’s fixed cell kind.
Returns None if id is not a live cell in this sheet.
§Examples
See the graph example.
Sourcepub fn add_relationship(
&mut self,
methods: Vec<Method>,
) -> Result<RelationshipId, Error>
pub fn add_relationship( &mut self, methods: Vec<Method>, ) -> Result<RelationshipId, Error>
Registers a relationship defined by a non-empty list of methods.
All methods are validated: their declared TypeIds must match the
registered cells, and each method must have at least one output. A method
with no inputs is explicitly allowed: it defines a fixed point (a constant,
independent of every other cell) rather than a derivation.
On success the RelationshipId is added to each adjacent cell’s adjacency list.
A cell that appears in both a method’s inputs and its outputs is a self-referencing cell and is explicitly allowed.
§Errors
Error::InvalidMethod—methodsis empty, or a method has no outputs.Error::MismatchedMethodCells— some method’sinputs ∪ outputsdiffers from another method’s in the same relationship.Error::InvalidMethodOutputs— a method’s ownoutputslist names a cell more than once, or two methods in the same relationship have identical or nestedoutputssets. Overlapping non-nested output sets are allowed.Error::InvalidId— aCellIdin any method is not found in this sheet.Error::TypeMismatch— a method’s declaredTypeIddoes not match the cell’s registeredTypeId.Error::InvalidCellKind— a method’s output cell isSource- orOut-kind.
Guard independence is checked by Sheet::validate and by Sheet::propagate,
not by this mutator.
- Complexity: O(m² × c) where m is the total number of methods and c is the maximum number of cells per method, due to pairwise output-set comparison, plus O(A) when invalidation releases the final owners of prepared artifacts of total size A.
§Examples
See the graph example.
Sourcepub fn add_conditional<T: Any + PartialEq + 'static>(
&mut self,
source: MatchExpr,
branches: Vec<(Vec<T>, Vec<RelationshipId>)>,
default: Vec<RelationshipId>,
) -> Result<ConditionalId, Error>
pub fn add_conditional<T: Any + PartialEq + 'static>( &mut self, source: MatchExpr, branches: Vec<(Vec<T>, Vec<RelationshipId>)>, default: Vec<RelationshipId>, ) -> Result<ConditionalId, Error>
Registers a conditional that activates relationships based on the value of a match
subject: either a single existing cell, or a MatchExpr computed from multiple
input cells.
Each element of branches is (keys, relationships): when the match subject’s
value equals any key in keys, the branch’s relationships are added to the active
set for propagate. Branches are evaluated in definition order; first match wins.
default holds relationships activated when no branch matches; pass an empty Vec
for no default.
§Errors
Error::InvalidId— the match subject references a cell not in this sheet.Error::TypeMismatch— (expression match subject only) an input cell’s registered type doesn’t match the expression’s declared type for that input.Error::InvalidConditional— the match subject’s output type does not matchT; a referenced relationship does not exist; a relationship already appears in another conditional branch; or a branch has no keys.Error::InvalidCellKind— a referenced relationship is a fixedOutwriter.
Guard independence is checked by Sheet::validate and by Sheet::propagate,
not by this mutator.
- Complexity: O(I + K + E) time and O(K + R) space for match inputs I, branch keys K, referenced relationships R and their total method-output edges E, plus O(A) when invalidation releases final prepared artifacts of size A.
§Examples
See the graph example.
Sourcepub fn add_requirement(
&mut self,
cell: CellId,
requirement: Requirement,
) -> Result<RequirementId, Error>
pub fn add_requirement( &mut self, cell: CellId, requirement: Requirement, ) -> Result<RequirementId, Error>
Attaches a requirement to cell and returns its stable ID.
requirement.inputs may be any cells in the sheet, not only cell itself.
The predicate runs during propagation, so callers can report false results
separately from propagation failures.
§Errors
-
Error::InvalidId—cell, or one ofrequirement’s input cells, is not a live cell in this sheet. -
Error::TypeMismatch— an input’s declared type does not match its cell’s registered type. -
Error::InvalidRequirement— the input signature is invalid. -
Complexity: O(k) where k is
requirement’s input count, plus O(A) when invalidation releases the final owners of prepared artifacts of total size A.
§Examples
use adam_rs::{Requirement, Sheet};
let mut sheet = Sheet::new();
let cell = sheet.add_cell(1_i32);
let id = sheet.add_requirement(cell, Requirement::from_fn_1(cell, |x: &i32| Ok(*x > 0)))?;
assert_eq!(sheet.cell_requirements(cell), Some([id].as_slice()));Sourcepub fn add_out(
&mut self,
writer: Method,
requirements: Vec<Requirement>,
) -> Result<CellId, Error>
pub fn add_out( &mut self, writer: Method, requirements: Vec<Requirement>, ) -> Result<CellId, Error>
Registers writer as the sole producer of its one output cell, which becomes
an out cell: always derived by writer, never write()-able, but otherwise
an ordinary, freely-referenceable cell. requirements are attached to that
cell one at a time, in order, via Sheet::add_requirement.
§Errors
-
Error::InvalidOutput—writerdoes not have exactly one output cell. -
Error::InvalidCellKind— the writer’s output cell is alreadySourceorOutkind, or already claimed as some existing method’s output. -
Any error
Sheet::add_relationshiporSheet::add_requirementcan return. -
Postcondition: not atomic — if
add_requirementfails partway throughrequirements, the output cell is leftOut-kind,writeris already registered as its producer, and every requirement before the failing one is already attached; none of this is rolled back on error. -
Complexity: O(k + i + m²×c) where k is the number of requirements and i is their total input count, plus the cost of
add_relationshipforwriteralone (m = 1 method, c = cells in that method), plus O(A) when invalidation releases the final owners of prepared artifacts of total size A.
§Examples
use adam_rs::{Method, Requirement, Sheet};
let mut sheet = Sheet::new();
let input = sheet.add_cell(1_i32);
let output = sheet.add_cell(0_i32);
let id = sheet.add_out(
Method::from_fn_1_1(input, output, |x: &i32| Ok(*x)),
vec![Requirement::from_fn_1(output, |x: &i32| Ok(*x > 0))],
)?;
assert_eq!(id, output);
sheet.propagate()?;
assert!(sheet.cell_requirements_valid(output));Sourcepub fn add_filter(&mut self, cell: CellId, filter: Filter) -> Result<(), Error>
pub fn add_filter(&mut self, cell: CellId, filter: Filter) -> Result<(), Error>
Attaches filter to cell.
Never evaluates filter’s function — attaching a filter is not a fresh
external input, so it never changes cell’s current effective value. The next
full Sheet::propagate call conforms cell via the planner’s
PlanStep::FilterReclamp step; until then, read() reflects whatever cell
held before this call.
§Errors
Error::InvalidId—cell, or one offilter’s argument cells, is not a live cell in this sheet.Error::InvalidFilter—cellalready has a filter,filter’s own value type does not matchcell’s registered type, orfilter’s argument list namescellitself.Error::TypeMismatch— an argument cell’s registered type does not match the typefilterdeclared for it.
Guard independence is checked by Sheet::validate and by Sheet::propagate,
not by this mutator.
- Complexity: O(a) where a is the number of filter argument cells, plus O(A) when invalidation releases the final owners of prepared artifacts of total size A.
§Examples
See the graph example.
Sourcepub fn filter_args(&self, id: CellId) -> Option<&[CellId]>
pub fn filter_args(&self, id: CellId) -> Option<&[CellId]>
Returns the argument cells of id’s filter, in declaration order.
Returns None if id is not a live cell in this sheet, or has no filter.
§Examples
See the graph example.
Sourcepub fn filter_dependents(&self, id: CellId) -> &[CellId]
pub fn filter_dependents(&self, id: CellId) -> &[CellId]
Returns the live cells whose filter references id as one of its dynamic
arguments — the reverse of a filter’s own argument list (Sheet::filter_args).
- Postcondition: empty if no live cell’s filter references
id.
§Examples
See the graph example.
Sourcepub fn filter_kind(&self, id: CellId) -> Option<&FilterKind>
pub fn filter_kind(&self, id: CellId) -> Option<&FilterKind>
Returns the kind of validation/derivation id’s filter performs, if it has one.
Returns None if id is not a live cell in this sheet, or has no filter.
§Examples
See the graph example.
Sourcepub fn filter_range<T: Any>(&self, id: CellId) -> Option<(T, T)>
pub fn filter_range<T: Any>(&self, id: CellId) -> Option<(T, T)>
Returns id’s filter’s current (lo, hi) bounds, if it has a FilterKind::Range
filter.
Resolves the filter’s argument cells’ current effective values via the same path
Sheet::add_filter already uses, then calls the filter’s bounds function.
Returns None if id is not a live cell in this sheet, has no filter, its filter’s
kind isn’t FilterKind::Range, its bounds have a different type from T,
or the range expression fails to evaluate against
the filter’s current argument values (e.g. a fallible arithmetic op in a range
endpoint) — the same degraded-to-None outcome as any other reason no live bounds
are available right now.
- Complexity: O(a) where a is the number of the filter’s argument cells.
§Examples
See the graph example.
Sourcepub fn filter_violation(&self, id: CellId) -> Option<&FilterViolation>
pub fn filter_violation(&self, id: CellId) -> Option<&FilterViolation>
Returns the filter violation recorded for id as of the last full
propagate() call, if any.
- Postcondition:
Noneifidhas no filter,id’s filter’s last-checked value held, or no fullpropagate()has run sinceidwas last a plain external write.
§Examples
See the graph example.
Sourcepub fn filter_violated_cells(&self) -> impl Iterator<Item = CellId> + '_
pub fn filter_violated_cells(&self) -> impl Iterator<Item = CellId> + '_
Iterates cells whose filter is currently violated, as of the last full
propagate() call.
- Complexity: O(n) where n is the number of currently-violated filters.
§Examples
See the graph example.
Sourcepub fn filter_violation_cells(&self) -> HashSet<CellId>
pub fn filter_violation_cells(&self) -> HashSet<CellId>
Returns the set of root cells currently determining a violated filter’s own
value or any of its argument values, as of the last full propagate() call —
the same “which upstream cells caused this” query
requirement_contributing_cells/requirement_violation_cells already provide
for Requirement.
- Postcondition: empty if no filter is currently violated.
- Complexity: O(sum of
contributing_cellscost over every violated filter and its argument cells).
§Examples
See the graph example.
Sourcepub fn cell_requirements(&self, id: CellId) -> Option<&[RequirementId]>
pub fn cell_requirements(&self, id: CellId) -> Option<&[RequirementId]>
Returns the requirements attached to id, in attachment order.
Returns None if id is not a live cell in this sheet.
§Examples
See the graph example.
Sourcepub fn requirement_cell(&self, id: RequirementId) -> Option<CellId>
pub fn requirement_cell(&self, id: RequirementId) -> Option<CellId>
Returns the cell requirement id is attached to.
Returns None if id is not a live requirement in this sheet.
§Examples
See the graph example.
Sourcepub fn requirement_inputs(&self, id: RequirementId) -> Option<&[CellId]>
pub fn requirement_inputs(&self, id: RequirementId) -> Option<&[CellId]>
Returns the cells requirement id reads.
Returns None if id is not a live requirement in this sheet.
§Examples
See the graph example.
Sourcepub fn cell_requirements_valid(&self, id: CellId) -> bool
pub fn cell_requirements_valid(&self, id: CellId) -> bool
Returns true if every requirement on id held as of the last propagate()
call.
Returns false if no propagation has run yet. Also returns true for an
id that is not a live cell in this sheet, since no requirement can have
failed for a cell that doesn’t exist.
§Examples
See the graph example.
Sourcepub fn violated_requirements(
&self,
id: CellId,
) -> impl Iterator<Item = RequirementId> + '_
pub fn violated_requirements( &self, id: CellId, ) -> impl Iterator<Item = RequirementId> + '_
Iterates the requirements on id that evaluated to false as of the last
propagate() call.
- Postcondition: empty if
id’s requirements all held,idis not a live cell in this sheet, or no propagation has run yet.
§Examples
See the graph example.
Sourcepub fn contributing_cells(&self, id: CellId) -> HashSet<CellId>
pub fn contributing_cells(&self, id: CellId) -> HashSet<CellId>
Returns the set of root cells that could determine id’s value for some choice of
cell strengths, as of the last propagate() call.
A cell is a root candidate for current whenever some active relationship
adjacent to current has a method producing it — not just the one method the
current strengths happen to have selected: a different strength ordering could pick
a different method of that same relationship, making any of its other cells the
source instead. So every method (of every active relationship touching current)
with current among its outputs contributes its inputs to the walk, not only the
currently-selected one. A cell outside Sheet::is_forced’s pure-output
classification contributes its own stored aspiration. This includes a
self-referencing input even when a mandatory writer prevents the cell from
remaining unclaimed: its seed can still depend on that aspiration.
Every visited cell is also checked against every conditional in the sheet: if any of that conditional’s branches (or its default) has a method whose outputs include the visited cell, the conditional’s match cell is added as a contributor too, and is itself traced recursively. This covers both an active producer that happens to belong to a conditional branch, and a cell with no active producer specifically because the branch that would define it isn’t the one currently selected (that absence is itself a fact controlled by the match cell, not an indication that the match cell is irrelevant).
A cell that can remain a source also contributes its dynamic filter arguments: the filter determines its effective value. Filters on outputs claimed in every acyclic assignment, including self-referencing outputs, only diagnose values and do not add value dependencies.
Eligibility searches use the candidate’s complete input/output relationship component within the last successful active set. That completed assignment witnesses feasibility of the independent components. Registered methods are never replaced or removed, so writes, appended structure and failed propagation preserve this witness; newly registered relationships stay outside these assignment searches until successful propagation publishes another active set. Filter arguments and potential conditional contributors still come from the current registered graph.
-
Postcondition: returns
{id}if no propagation has run yet, or ifidis not forced and no active or conditional relationship or dynamic filter could produce it. -
Complexity: O(N · (R·M·K² + B·M·K)) where N is the number of cells reachable from
id(including conditional match cells), R is the number of relationships adjacent to a visited cell, M is the maximum number of methods per relationship, K is the maximum input/output count per method, and B is the total number of branch/default relationships across all conditionals, plus the number of traversed filter argument edges. Each visited non-forced filtered cell with dynamic arguments that is not currently a source adds at most one exact eligibility search after expected linear traversal of that candidate’s component adjacency (including inactive adjacency entries). Each search takes expected O(T + U · (1 + A · K²)) time, where T is relationship storage slots scanned, A is active relationships in that component, and U counts that search’s recursive calls and examined method candidates. U is bounded by O(1 + M + … + M^A), including partial checks when M = 1; disconnected components do not multiply its search tree. Debug builds add O(G) validation during each component discovery, where G is prepared active relationships. Traversal storage is O(N + E) for visited cells and queued dependency edges E, plus visited component cells/relationships and O(1 + A · (1 + K)) auxiliary storage for one eligibility search. Repeated queries do not cache searches.
§Examples
See the graph example.
Sourcepub fn requirement_contributing_cells(
&self,
id: RequirementId,
) -> HashSet<CellId>
pub fn requirement_contributing_cells( &self, id: RequirementId, ) -> HashSet<CellId>
Returns the union of Sheet::contributing_cells over requirement id’s own
declared inputs.
Returns an empty set if id is not a live requirement in this sheet.
- Complexity: The sum of
Self::contributing_cellscosts over the requirement’s inputs, with O(N) space for their union of N root cells.
§Examples
See the graph example.
Sourcepub fn requirement_relevant_cells(&self) -> HashSet<CellId>
pub fn requirement_relevant_cells(&self) -> HashSet<CellId>
Returns the union of contributing_cells over every cell with at least one
requirement — the set of cells currently determining at least one
requirement-checked value, as of the last propagate() call.
- Postcondition: empty if no cell in the sheet has any requirements.
- Complexity: O(sum of
contributing_cellscost over every cell with requirements).
§Examples
See the graph example.
Sourcepub fn requirement_violation_cells(&self) -> HashSet<CellId>
pub fn requirement_violation_cells(&self) -> HashSet<CellId>
Returns the union of requirement_contributing_cells over every requirement
that evaluated false as of the last propagate() call, across every cell
in the sheet.
- Postcondition: empty if no requirement anywhere in the sheet currently fails.
- Complexity: O(sum of
requirement_contributing_cellscost over every violated requirement).
§Examples
See the graph example.
Sourcepub fn write<T: Any + 'static>(
&mut self,
id: CellId,
value: T,
) -> Result<(), Error>
pub fn write<T: Any + 'static>( &mut self, id: CellId, value: T, ) -> Result<(), Error>
Writes a value to a cell, incrementing the cell’s write-recency strength.
Each successful write increments a global monotonic counter and assigns
the new value to cell.strength, so the most-recently-written cell always
has the highest strength.
- Postcondition: any pending derived override is cleared, so the written value is immediately visible via
read(). - Complexity: Expected O(1) validation and phase-membership work, plus O(A) when a claimed-cell write releases the final owners of prepared artifacts of total size A, excluding destruction of replaced values.
§Errors
Error::InvalidId—idis not a cell in this sheet.Error::TypeMismatch—Tdoes not match the cell’s registeredTypeId.Error::InvalidCellKind—idisOut-kind.
§Examples
See the graph example.
Sourcepub fn read<T: Any + 'static>(&self, id: CellId) -> Result<&T, Error>
pub fn read<T: Any + 'static>(&self, id: CellId) -> Result<&T, Error>
Returns a shared reference to the cell’s effective current value: its derived override if one exists, otherwise its source (last written) value.
§Errors
Error::InvalidId—idis not a cell in this sheet.Error::TypeMismatch—Tdoes not match the cell’s registeredTypeId.
§Examples
See the graph example.
Sourcepub fn source<T: Any + 'static>(&self, id: CellId) -> Result<&T, Error>
pub fn source<T: Any + 'static>(&self, id: CellId) -> Result<&T, Error>
Returns the raw source slot: the last value written via write()/add_cell,
ignoring any derived override produced by a self-referencing method or a
conditionally forced relationship. For an ordinary (unshadowed) derived cell,
propagate() writes straight into this same slot, so source() agrees with
read(); the two diverge only for cells currently shadowed.
§Errors
Error::InvalidId—idis not a cell in this sheet.Error::TypeMismatch—Tdoes not match the cell’s registeredTypeId.
§Examples
use adam_rs::Sheet;
let mut sheet = Sheet::new();
let a = sheet.add_cell(3_i32);
sheet.write(a, 8_i32).unwrap();
assert_eq!(*sheet.source::<i32>(a).unwrap(), 8);Sourcepub fn changed(&self) -> impl Iterator<Item = CellId> + '_
pub fn changed(&self) -> impl Iterator<Item = CellId> + '_
Iterates over cells updated during the most recent successful propagate() call.
This includes cells written by selected methods and cells that reverted to their source values because the relationship that had been shadowing them (self-referencing or conditionally forced) is no longer producing them this round (Phase 5), even though no method wrote to them this round. It does not attempt to compare old/new values for equality.
- Complexity: O(n) where n is the number of changed cells.
§Examples
See the graph example.
Sourcepub fn clear_changed(&mut self)
pub fn clear_changed(&mut self)
Clears the changed-cell set and resets each cell’s changed flag.
Call after processing the results of propagate().
- Complexity: O(n) where n is the number of changed cells.
§Examples
See the graph example.
Sourcepub fn validate(&mut self) -> Result<(), Error>
pub fn validate(&mut self) -> Result<(), Error>
Validates static guard independence for the current sheet structure.
A filter’s argument cells and a conditional’s match cells must not depend on any cell governed by that filter or conditional in the static dependency graph. A successful call records the current structure as validated.
§Errors
Error::DependencyCycle— a guard edge lies on a cycle in the static dependency graph.
§Examples
use adam_rs::{Filter, Method, Sheet};
let mut sheet = Sheet::new();
let limit = sheet.add_cell(10_i32);
let value = sheet.add_cell(5_i32);
let copy = sheet.add_cell(0_i32);
sheet
.add_relationship(vec![Method::from_fn_1_1(value, copy, |x: &i32| Ok(*x))])
.unwrap();
sheet
.add_filter(value, Filter::from_fn_1(limit, |v: &i32, hi: &i32| Ok((*v).min(*hi))))
.unwrap();
sheet.validate().unwrap();- Complexity: O(1) when no structural mutation has happened since the last successful validation; otherwise O(V + E), where V is the number of cells and E is the number of static method and guard edges.
Sourcepub fn cells(&self) -> impl Iterator<Item = CellId> + '_
pub fn cells(&self) -> impl Iterator<Item = CellId> + '_
Iterates all live cell IDs in the sheet.
- Complexity: O(n) where n is the number of cells.
§Examples
See the graph example.
Sourcepub fn relationships(&self) -> impl Iterator<Item = RelationshipId> + '_
pub fn relationships(&self) -> impl Iterator<Item = RelationshipId> + '_
Iterates all live relationship IDs in the sheet.
- Complexity: O(n) where n is the number of relationships.
§Examples
See the graph example.
Sourcepub fn cell_adj(&self, id: CellId) -> Option<&[RelationshipId]>
pub fn cell_adj(&self, id: CellId) -> Option<&[RelationshipId]>
Returns the relationships adjacent to id.
Returns None if id is not a live cell in this sheet.
- Complexity: O(1).
§Examples
See the graph example.
Sourcepub fn relationship_adj(&self, id: RelationshipId) -> Option<&[CellId]>
pub fn relationship_adj(&self, id: RelationshipId) -> Option<&[CellId]>
Returns the cells adjacent to id (union across all methods).
Returns None if id is not a live relationship in this sheet.
- Complexity: O(1).
§Examples
See the graph example.
Sourcepub fn propagate(&mut self) -> Result<(), Error>
pub fn propagate(&mut self) -> Result<(), Error>
Updates the sheet using eligible prepared plans or newly selected methods.
Validates static guard independence before mutating state, then clears the
changed-cell set from the previous propagate() call before staged planning.
After propagation, call Sheet::changed to inspect which cells were updated,
and Sheet::clear_changed when done.
Method, filter, conditional-expression, and requirement callbacks must be purely functional. There is no guarantee that any callback executes, or when or how often it executes; results may be reused or recomputed while preserving cell values and diagnostics. The phases below describe the current algorithm, not a callback invocation contract.
Retains at most one prepared unconditional plan and one prepared main plan, each with its own released sources and active relationships. Successful writes to released sources preserve that phase’s eligibility; writes to claimed cells (including self-referencing claimants) discard it. Filter reclamps do not claim released sources. Actual structural mutations discard both entries. Prepared plans retain structural seed recipes, not values from earlier calls.
Phase 0 — Staging: propagation starts from each cell’s source value, excluding every derived override from the previous round, but does not mutate live cells.
Phase 1 — Pre-plan: when conditionals exist, an eligible prepared unconditional plan is obtained or a new one is prepared. Its selected guard-prerequisite cone executes so derived guard values are current before branch evaluation. Unrelated filters remain deferred until the final active plan, after the relationships that produce their arguments.
Phase 2 — Conditional evaluation: each conditional’s match cell value is read and compared against branch keys; the active relationship set is built.
Phase 3 — General plan: an eligible prepared main plan is obtained for the active set, or the Adam algorithm selects and prepares a new one. Seed-cycle validation and method execution consume staged Phase 1 values. A callback already evaluated in Phase 1 is reused only when the final plan preserves its selected method, input producers, and output classification. A mismatched plan returns a conservative conflict rather than committing stale values. This boundary may reject a sheet even when another assignment could have avoided the mismatch; propagation reports the implicated sites instead of attempting that alternate assignment. A Phase 1 seed callback is likewise reused only when every input reads the same staged source version, recursive seed result, or accumulated value; otherwise propagation returns a conflict instead of reusing a stale seed.
Phase 4 — Commit and strength post-processing: after planning, seed validation, and staged method execution all succeed, staged writes are published atomically. Derived cells then receive low-order strengths in evaluation order, enforcing the stability invariant. A cell claimed self-referencingly keeps any live explicit strength instead, since its own written value is still the authority behind the result.
Phase 5 — Reversion change-tracking: a cell whose derived override existed before this round but wasn’t reclaimed by any method this round has effectively reverted to its source value (e.g. its forcing conditional went inactive); it is marked changed even though no method wrote to it this round.
Phase 6 — Requirement evaluation: every registered requirement is evaluated
against current cell values, rebuilding last_requirement_violations from
scratch, so Sheet::cell_requirements_valid and Sheet::violated_requirements
reflect this round.
Filter failures remain diagnostics rather than propagation errors, and diagnostic results use current values.
- Complexity: on a cache miss, prerequisite-cone indexing, traversal, and producer preparation take O(V + E) per selected plan, distinct from assignment matching. Cache hits omit this preparation, assignment matching, global strength sorting, and complete seed-elimination replay. Active-set comparison takes expected O(R) for R active relationships; a pending strength certificate takes O(C) for C cells. Current strength gates and local sibling sorts still run (O(S log S) comparisons for S siblings, with structural signature comparison costs). Fresh staged execution, seed evaluation, commit, and diagnostics retain their costs. Discarding final owners of prepared artifacts of total size A can take O(A).
§Errors
Error::DependencyCycle— a filter or conditional guard edge lies on a cycle in the static dependency graph.Error::Conflict— no valid method assignment exists, or a pre-executed guard prerequisite is incompatible with the final selected plan.Error::SeedCycle— sibling seed dependencies form a non-self cycle.Error::MethodFailed— a method’s function returned an error, a method produced the wrong number of outputs, or a requirement’s function returned an error. A requirement error includes its governed cell as an error site.Error::TypeMismatch— a method output’s runtime type does not match the cell’s registered type.
Sourcepub fn selected_method(&self, rel: RelationshipId) -> Option<usize>
pub fn selected_method(&self, rel: RelationshipId) -> Option<usize>
Returns the index of the method selected for rel in the last successful propagation.
Returns None if no propagation has run yet, rel is not in the cached plan,
or rel was added after the last propagate() call.
- Complexity: O(S) worst-case time for S completed execution steps; O(1) space.
§Examples
See the graph example.
Sourcepub fn method_inputs(
&self,
rel: RelationshipId,
idx: usize,
) -> Option<&[CellId]>
pub fn method_inputs( &self, rel: RelationshipId, idx: usize, ) -> Option<&[CellId]>
Returns the input cells of method idx in relationship rel.
Returns None if rel is not a live relationship or idx is out of bounds.
§Examples
See the graph example.
Sourcepub fn method_outputs(
&self,
rel: RelationshipId,
idx: usize,
) -> Option<&[CellId]>
pub fn method_outputs( &self, rel: RelationshipId, idx: usize, ) -> Option<&[CellId]>
Returns the output cells of method idx in relationship rel.
Returns None if rel is not a live relationship or idx is out of bounds.
§Examples
See the graph example.
Sourcepub fn is_source(&self, id: CellId) -> bool
pub fn is_source(&self, id: CellId) -> bool
Returns true if id was not written by any selected method in the last successful propagation.
Returns false if no propagation has succeeded yet. This display query does not
determine prepared-plan eligibility.
Cells absent from the completed plan, including newly added cells, return true.
- Complexity: Expected O(1).
§Examples
See the graph example.
Sourcepub fn source_cells(
&self,
results: &[CellId],
) -> impl Iterator<Item = CellId> + use<'_>
pub fn source_cells( &self, results: &[CellId], ) -> impl Iterator<Item = CellId> + use<'_>
Iterates snapshot sources contributing to results in the last successful plan.
An empty results slice selects the entire sheet: unclaimed cells and outputs
read by their own selected method. Otherwise, selects only those sources in
the requested cells’ selected producer paths, including conforming filter
arguments and conditional selectors governing those paths. Unselected methods’
inputs and diagnostic-only filter arguments do not contribute.
Each source appears once, in unspecified order, even when results repeat or
share dependencies. Pure derived outputs and Out cells are excluded.
Returns an empty iterator before any successful propagation. Like
Self::is_source, classification describes the last successful plan;
newly added non-Out cells count as unclaimed.
To capture a settled result, iterate after successful propagation and before
further edits, reading effective values with Self::read, not the
aspirations returned by Self::source. Clients own baseline comparison,
relevance filtering, stable cell identity, and typed serialization.
Replay writes all captured values in any order as one batch before
propagation. For a settled solution satisfying its relationships and filters,
a compatible graph with matching uncaptured inputs reproduces the requested
effective values (or all values when results is empty). Relationship methods
must preserve an already-satisfied assignment, and self-referencing methods
and filters must be idempotent;
these caller contracts make the solution a fixed point even if replay
selects different methods. No strengths or write history need to be captured.
A snapshot does not preserve original aspirations, priority history,
diagnostics, or behavior under subsequent edits, including spring-back.
- Precondition: every result is a live cell belonging to this sheet.
- Complexity: empty results take O(C + S + Σ(I × O)) traversal time and O(1) auxiliary space, for C cells, S selected steps, and each selected method’s I inputs and O outputs. Nonempty results add expected O(N + V + E + V × G) time and O(N + V + E) auxiliary space, for N requested results, V reachable value/producer pairs, E their dependency edges, and G the total size of the registered conditional graph. Does not copy cell values.
§Examples
use adam_rs::Sheet;
let mut sheet = Sheet::new();
let width = sheet.add_source(10_i32);
sheet.add_source(false);
sheet.propagate()?;
assert_eq!(sheet.source_cells(&[]).count(), 2);
let capture = sheet.source_cells(&[width])
.map(|id| Ok((id, *sheet.read::<i32>(id)?)))
.collect::<Result<Vec<_>, adam_rs::Error>>()?;
assert_eq!(capture, [(width, 10)]);
// Use host-defined names or schema keys, not live CellIds, for persistence.Sourcepub fn is_forced(&self, id: CellId) -> bool
pub fn is_forced(&self, id: CellId) -> bool
Returns whether id belongs to the last successful plan’s forced pure-output set.
The planner intersects pure outputs across viable methods and eliminates alternatives that conflict with another relationship’s forced outputs until the classification stabilizes. A forced pure output’s producer does not read that cell as a self-referencing input.
A conditionally forced cell retains its pre-forcing priority and source aspiration for release; see the forcing outcomes.
This does not decide whether some assignment can leave the cell unclaimed. A mandatory self-referencing writer claims its output exclusively but remains outside this classification; its editable source aspiration can affect its seed.
Returns false if no propagation has run yet.
§Examples
See the graph example.
Sourcepub fn forced_cells(&self) -> impl Iterator<Item = CellId> + '_
pub fn forced_cells(&self) -> impl Iterator<Item = CellId> + '_
Iterates cells that are forced (see Sheet::is_forced) as of the last
successful propagate() call.
- Complexity: O(n) where n is the number of forced cells.
§Examples
See the graph example.
Sourcepub fn is_relationship_forced(&self, id: RelationshipId) -> bool
pub fn is_relationship_forced(&self, id: RelationshipId) -> bool
Returns true if id had exactly one viable method as of the last successful
propagate() call — the planner has no alternative method to choose for this
relationship, regardless of cell strength.
Returns false if no propagation has run yet.
§Examples
See the graph example.
Sourcepub fn forced_relationships(&self) -> impl Iterator<Item = RelationshipId> + '_
pub fn forced_relationships(&self) -> impl Iterator<Item = RelationshipId> + '_
Iterates relationships that are forced (see Sheet::is_relationship_forced)
as of the last successful propagate() call.
- Complexity: O(n) where n is the number of forced relationships.
§Examples
See the graph example.
Sourcepub fn conditionals(&self) -> impl Iterator<Item = ConditionalId> + '_
pub fn conditionals(&self) -> impl Iterator<Item = ConditionalId> + '_
Iterates all live conditional IDs in the sheet.
- Complexity: O(n) where n is the number of conditionals.
§Examples
See the graph example.
Sourcepub fn out_cells(&self) -> impl Iterator<Item = CellId> + '_
pub fn out_cells(&self) -> impl Iterator<Item = CellId> + '_
Iterates all live Out-kind cells in the sheet.
- Complexity: O(n) where n is the number of cells.
§Examples
See the graph example.
Sourcepub fn conditional_match_cells(&self, id: ConditionalId) -> Option<&[CellId]>
pub fn conditional_match_cells(&self, id: ConditionalId) -> Option<&[CellId]>
Returns the match cells for conditional id: a single cell for a plain match
subject, or every input of a MatchExpr match subject.
Returns None if id is not a live conditional in this sheet.
§Examples
See the graph example.
Sourcepub fn conditional_branch_count(&self, id: ConditionalId) -> Option<usize>
pub fn conditional_branch_count(&self, id: ConditionalId) -> Option<usize>
Returns the number of named branches in conditional id.
Returns None if id is not a live conditional in this sheet.
§Examples
See the graph example.
Sourcepub fn conditional_branch_relationships(
&self,
id: ConditionalId,
branch: usize,
) -> Option<&[RelationshipId]>
pub fn conditional_branch_relationships( &self, id: ConditionalId, branch: usize, ) -> Option<&[RelationshipId]>
Returns the relationship IDs for branch branch of conditional id.
Returns None if id is not a live conditional, or branch is out of bounds.
§Examples
See the graph example.
Sourcepub fn conditional_default_relationships(
&self,
id: ConditionalId,
) -> Option<&[RelationshipId]>
pub fn conditional_default_relationships( &self, id: ConditionalId, ) -> Option<&[RelationshipId]>
Returns the default relationship IDs for conditional id.
These relationships are active when no named branch key matches the match cell.
Returns None if id is not a live conditional in this sheet.
§Examples
See the graph example.
Sourcepub fn conditional_active_branch(
&self,
id: ConditionalId,
) -> Result<Option<usize>, Error>
pub fn conditional_active_branch( &self, id: ConditionalId, ) -> Result<Option<usize>, Error>
Returns the index of the currently matching branch for conditional id.
Evaluates branch keys against the match subject’s current value in definition
order; returns the index of the first matching branch. Returns Ok(None) if no
branch key matches (the default branch is active) or if id is not a live
conditional.
§Errors
-
Error::MethodFailed—idis a live, expression-sourced conditional whose function returned an error. The original cause andErrorSite::Conditional(id)identify the failing execution. -
Complexity: O(B·K) where B = branches, K = keys per branch.
§Examples
See the graph example.