Use case
A code generator (or a user debugging a model) needs to ask an expression basic questions: which symbols does it depend on (to declare inputs), how big is it (to decide CSE / caching / whether a tangent is worth emitting symbolically), what annotations does it carry (to print or serialize them), and to walk it with a callback without writing a 20-handler visitor subclass.
What happens today (probed on main, compile-time checks)
free_symbols(expr) -> no such API
node_count(expr), depth(expr) -> no such API
for_each_node(expr, callback) -> no such API
to_string(A.get().tensor_algebra_assumptions()) -> no such API (assumptions cannot be printed)
contains_expression/depends_on_tensor exist for one symbol at a time, and the rebuild visitors can be subclassed, but there is no read-only traversal utility and no way to enumerate the symbol set of an expression. numsim-codegen has to reimplement traversal per domain against the private node list.
Proposal
A small introspection.h in core/, one overload set per domain (the visitor pattern makes each a ~30-line const visitor):
// symbols the expression depends on, per domain; sorted, deduplicated
struct symbol_set { std::vector<S> scalars; std::vector<T> tensors; };
symbol_set free_symbols(expression_holder<Any> const&);
// size metrics: distinct nodes (DAG) vs tree nodes, and depth
struct expression_metrics { std::size_t nodes, distinct_nodes, depth; };
expression_metrics metrics(expression_holder<Any> const&);
// read-only pre-order walk over all three domains (crosses domain edges)
void for_each_node(expression_holder<Any> const&, std::function<void(expression const&)>);
// printable annotations
std::string to_string(numeric_assumption_manager const&);
std::string to_string(tensor_algebra_assumption_set const&);
std::string to_string(tensor_space const&);
nodes vs distinct_nodes is the number #472 cares about (sharing flattened vs preserved). for_each_node should use the memoization pattern from #441 (pointer-keyed visited set) so shared DAGs are linear.
Scope
Small–medium. No new nodes; pure visitors. Also the natural home for the "assumption listing" that #385 (API docs) and #384 (serialization: annotations must be emitted) both need.
Signed-off-by: petlenz [email protected]
Use case
A code generator (or a user debugging a model) needs to ask an expression basic questions: which symbols does it depend on (to declare inputs), how big is it (to decide CSE / caching / whether a tangent is worth emitting symbolically), what annotations does it carry (to print or serialize them), and to walk it with a callback without writing a 20-handler visitor subclass.
What happens today (probed on main, compile-time checks)
contains_expression/depends_on_tensorexist for one symbol at a time, and the rebuild visitors can be subclassed, but there is no read-only traversal utility and no way to enumerate the symbol set of an expression. numsim-codegen has to reimplement traversal per domain against the private node list.Proposal
A small
introspection.hincore/, one overload set per domain (the visitor pattern makes each a ~30-line const visitor):nodesvsdistinct_nodesis the number #472 cares about (sharing flattened vs preserved).for_each_nodeshould use the memoization pattern from #441 (pointer-keyed visited set) so shared DAGs are linear.Scope
Small–medium. No new nodes; pure visitors. Also the natural home for the "assumption listing" that #385 (API docs) and #384 (serialization: annotations must be emitted) both need.
Signed-off-by: petlenz [email protected]