Skip to content

Expression introspection API: free_symbols, metrics, for_each_node, printable assumptions #498

Description

@petlenz

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]

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions