free_variables
Stable function · Expressions
Tier: stable · Kind: function · Topic: Expressions
| Julia | TypeScript | Python | Rust | Go |
|---|---|---|---|---|
free_variables | freeVariables | free_variables | free_variables | FreeVariables |
Julia
free_variables(expr::NumExpr)
free_variables(expr::IntExpr)
free_variables(expr::VarExpr)
free_variables(expr::OpExpr)
free_variables(expr::ASTExpr)::Set{String}
Extract all free (unbound) variable names from an expression. Returns a set of variable names that appear in the expression.
Examples
x = VarExpr("x")
y = VarExpr("y")
sum_expr = OpExpr("+", [x, y])
vars = free_variables(sum_expr) # Set(["x", "y"])
nested = OpExpr("*", [OpExpr("+", [x, NumExpr(1.0)]), y])
vars = free_variables(nested) # Set(["x", "y"])
Example (expressions)
# Parse from text, or build the tree directly.
expr = parse_expression("k * x + y")
built = OpExpr("+", [OpExpr("*", [VarExpr("k"), VarExpr("x")]), VarExpr("y")])
@assert to_ascii(expr) == to_ascii(built) == "k * x + y"
@assert free_variables(expr) == Set(["k", "x", "y"])
# `substitute` takes a Dict{String,ASTExpr}: give the value type explicitly.
replaced = substitute(expr, Dict{String,ASTExpr}("y" => parse_expression("2 * x")))
@assert to_ascii(replaced) == "k * x + 2 * x"
@assert free_variables(replaced) == Set(["k", "x"])
# Constant subexpressions fold away.
@assert to_ascii(simplify(parse_expression("x * (1 + 1)"))) == "x * 2"
# An equation parses the same way, with D(x, t) for a time derivative.
eq = parse_equation("D(x, t) = -k * x")
@assert eq isa Equation
@assert to_unicode(eq) == "∂x/∂t = −k·x"
TypeScript
function freeVariables(expr: Expr): Set<string>
Extract all variable references from an expression.
Routes through forEachChild — the ONE walker — so every
expression-bearing field is seen, not just args. Walking args alone made
whole sidecar subtrees invisible:
table_lookup{axes:{temp:'T_air'}}→[](missedT_air)aggregate{args:['A'], expr:{*:['A','w']}}→['A'](missedw)integral{lower:'a', upper:{+:['b',1]}}→ misseda,b
graph.ts builds the dependency DAG from this, so every one of those misses
was a silently-absent edge.
Example (expressions)
// Expressions are plain JSON trees; build one directly ...
const built: Expr = { op: '*', args: ['k', { op: '-', args: ['T_wall', 'T_air'] }] }
// ... or parse the text form, which is the inverse of `toAscii`.
const parsed = parseExpression('k * (T_wall - T_air)')
assert.deepEqual(parsed, built)
assert.deepEqual([...freeVariables(parsed)].sort(), ['T_air', 'T_wall', 'k'])
assert.equal(contains(parsed, 'T_wall'), true)
// Substitution is single-pass and returns a new tree.
const bound = substitute(parsed, { T_wall: 300, k: 0.05 })
assert.equal(toAscii(bound), '0.05 * (300 - T_air)')
assert.deepEqual([...freeVariables(bound)], ['T_air'])
assert.equal(toAscii(simplify(substitute(parsed, { T_wall: 300, T_air: 300 }))), '0')
const eq = parseEquation('D(T_air) = k * (T_wall - T_air)')
assert.deepEqual(eq.rhs, built)
assert.throws(() => parseExpression('k * (T_wall'), ExpressionParseError)
Python
free_variables(expr: Expr) -> set[str]
Extract all free variables from an expression.
Traverses the full canonical child set (.expr_walk), so variables
hidden in aggregate bodies, filter predicates, integral bounds,
makearray values, or table_lookup axes are all found. wrt and
dim are not children (they bind/label, they do not reference values)
and are not collected.
Parameters
expr(Expr) — Expression to analyze
Returns
set[str]— Set of variable names found in the expression
Example (expressions)
# An expression is a number, a variable name, or an operator node.
exchange = ExprNode(op="*", args=["k", ExprNode(op="-", args=["T_wall", "T_air"])])
assert earthsci_ast.free_variables(exchange) == {"k", "T_wall", "T_air"}
# Text parses to the same tree that to_ascii prints.
parsed = earthsci_ast.parse_expression("k * (T_wall - T_air)")
assert earthsci_ast.to_ascii(parsed) == earthsci_ast.to_ascii(exchange)
# Substitution replaces names with numbers or with other expressions.
fixed = earthsci_ast.substitute(exchange, {"k": 0.05, "T_wall": ExprNode(op="+", args=["T0", 10])})
assert earthsci_ast.to_ascii(fixed) == "0.05 * (T0 + 10 - T_air)"
assert earthsci_ast.free_variables(fixed) == {"T0", "T_air"}
Rust
pub fn free_variables(expr: &Expr) -> HashSet<String>
Extract all free variables from an expression
Arguments
expr- The expression to analyze
Returns
- Set of variable names referenced in the expression
Example (expressions)
// Parse the text form (the same syntax `to_ascii` prints) ...
let rate = parse_expression("k * A - x")?;
assert_eq!(to_ascii(&rate), "k * A - x");
// ... or a whole equation.
let eq = parse_equation("D(x)/Dt = k * A - x")?;
assert_eq!(to_ascii(&eq.lhs), "D(x)/Dt");
let mut names: Vec<String> = free_variables(&rate).into_iter().collect();
names.sort();
assert_eq!(names, ["A", "k", "x"]);
// Replace `k` with `2 * j`.
let subs = HashMap::from([("k".to_string(), parse_expression("2 * j")?)]);
let replaced = substitute(&rate, &subs);
assert_eq!(to_ascii(&replaced), "2 * j * A - x");
// Expressions are also plain Rust values.
let one = Expr::Variable("x".to_string());
assert_eq!(to_ascii(&one), "x");
Go
func FreeVariables(expr Expression) map[string]bool
FreeVariables returns the set of all variable names that appear in an expression, including those reachable ONLY through a sidecar (non-args) field — an aggregate’s expr/filter/key/join, an integral’s lower/upper, a table_lookup’s axes, a makearray’s values, an apply_expression_template’s bindings, … The walk is the shared field-preserving one (mapExprChildren), so a name can never again hide from it in a field the walker was not taught about (audit G1 — and G2, where a false-negative Contains made the DAE contract delete a still-referenced equation).
Example (expressions)
parsed, err := esm.ParseExpression("k * (T_wall - T_air)")
if err != nil {
log.Fatal(err)
}
fmt.Println(sortedKeys(esm.FreeVariables(parsed)))
// The same tree, built directly: an operator node is an ExprNode, a
// variable is a string and a number is a float64.
built := esm.ExprNode{Op: "*", Args: []any{
"k",
esm.ExprNode{Op: "-", Args: []any{"T_wall", "T_air"}},
}}
fmt.Println(esm.ToASCII(built) == esm.ToASCII(parsed))
substituted, err := esm.Substitute(parsed, map[string]esm.Expression{
"T_wall": 290.0,
"k": esm.ExprNode{Op: "/", Args: []any{1.0, "tau"}},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(esm.ToASCII(substituted))
fmt.Println(sortedKeys(esm.FreeVariables(substituted)))
value, err := esm.Evaluate(substituted, map[string]float64{"tau": 20, "T_air": 280})
if err != nil {
log.Fatal(err)
}
fmt.Println(value)