Expand description
§Control execution and release working memory
Ordinary operations reuse scratch buffers attached to the vtree. Use an explicit batch when you need resource limits; release idle scratch when you no longer need the retained memory.
We use four backup options: local L, remote R, encryption E, and notifications N.
First build L ∨ R, then require encryption to obtain (L ∨ R) ∧ E.
Notifications remain free. Run the complete program
with cargo run --example execution.
§Bound a batch of operations
Use the vtree’s Context to set a memory budget. A zero-byte
budget lets us demonstrate a refused allocation:
use std::sync::Arc;
use tididi::limits::LimitConfig;
use tididi::{literal, OperationError, Tdd, Vtree};
let vtree = Arc::new(Vtree::balanced(4));
let context = Arc::clone(vtree.context());
let limit = LimitConfig::none().with_memory_budget_bytes(Some(0));
let attempt = context.with_limits(limit, |operations| {
operations.clause(&vtree, [1, 2])
});Call through operations inside the batch: ordinary diagram methods and free
functions do not inherit its limits. The budget covers charged allocation
growth per operation; LimitConfig describes
what it measures. Handle a refusal like any other error:
match attempt {
Ok(diagram) => println!("Destination choices: {}", diagram.model_count()?),
Err(OperationError::OverBudget) => println!("Not enough budget to build the destination rule"),
Err(error) => return Err(error),
}Output:
Not enough budget to build the destination ruleThe budget ends with the batch. A later, unrestricted operation succeeds:
let destination = Tdd::clause(&vtree, [1, 2])?;
println!("Destination choices: {}", destination.model_count()?);Output:
Destination choices: 12Use Context::run for a batch with no
initial limits; its example shows several checked operations in one checkout.
§Keep inputs for a retry
Conjunction consumes its operands even when it returns an error. Pass clones if you need to preserve them for another attempt. Here we retry without a budget; an application could instead choose a larger limit or defer the work.
let encrypted = literal(&vtree, 3)?;
let limit = LimitConfig::none().with_memory_budget_bytes(Some(0));
let attempt = context.with_limits(limit, |operations| {
operations.and(destination.clone(), encrypted.clone())
});
let secured = match attempt {
Ok(diagram) => diagram,
Err(OperationError::OverBudget) => {
println!("Not enough budget; retrying with the original operands");
tididi::and(destination, encrypted)?
}
Err(error) => return Err(error),
};
println!("Secured choices: {}", secured.model_count()?);Output:
Not enough budget; retrying with the original operands
Secured choices: 6The three choices of destination each allow notifications on or off, giving six configurations with encryption enabled.
§Bound repeated queries
A counter can use batch limits while keeping its cached counts between queries. Bind it to the supplied engine for the duration of the query:
let mut counter = secured.counter()?;
let query_limit = LimitConfig::none().with_memory_budget_bytes(Some(1_000_000));
let bounded_count = context.with_limits(query_limit, |operations| {
counter.bind(operations).model_count()
})?;
println!("Count with a budget: {bounded_count}");Output:
Count with a budget: 6After the batch, the counter keeps its observations and cached counts.
Engine::counter creates one bound to an engine
from the start.
§Release idle scratch
Call clear_scratch between batches to
release idle buffers while keeping the diagrams:
context.clear_scratch();
println!("Circuit remains usable: {}", secured.is_sat()?);Output:
Circuit remains usable: trueDropping the last reference to a context also frees its idle buffers.