Version: dev

substitute

Stable function · Expressions

Tier: stable · Kind: function · Topic: Expressions

JuliaTypeScriptPythonRustGo
substitutesubstitutesubstitutesubstituteSubstitute

Julia

substitute(expr::NumExpr, bindings::Dict{String, ASTExpr})
substitute(expr::IntExpr, bindings::Dict{String, ASTExpr})
substitute(expr::VarExpr, bindings::Dict{String, ASTExpr})
substitute(expr::OpExpr, bindings::Dict{String, ASTExpr})
substitute(expr::ASTExpr, bindings::Dict{String,ASTExpr})::ASTExpr

Replace every variable named in bindings with its replacement expression, throughout expr (including faq bodies, filter predicates, integral bounds and every other sub-expression a node carries). A variable not in bindings is left unchanged. Substitution is single-pass: a replacement is inserted as is and never substituted again. Returns a new expression; expr is not modified.

bindings must have value type ASTExpr exactly: a Dict("x" => NumExpr(2.0)) is a Dict{String,NumExpr} and has no method, so build it as Dict{String,ASTExpr}(...).

Examples

e = parse_expression("k * (T_wall - T_air)")
bindings = Dict{String,ASTExpr}("T_wall" => NumExpr(300.5),
                                "k" => parse_expression("2 * k0"))
to_ascii(substitute(e, bindings))  # "2 * k0 * (300.5 - T_air)"

Source

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"

From docs/api/examples/julia/expressions.jl

TypeScript

function substitute(expr: Expr, bindings: Record<string, Expr>, context?: SubstitutionContext): Expr

Recursively substitute variable references in an expression with bound expressions. Handles scoped references (Model.Subsystem.var) by splitting on ‘.’ and matching path through system hierarchy per format spec Section 4.3.

NOTE: when a context is supplied, any dotted reference NOT covered by bindings is resolved through the file hierarchy and replaced with the referenced variable’s DECLARED DEFAULT VALUE. Callers that only want to rename/replace bound names must omit context.

Parameters

  • expr — Expression to substitute into
  • bindings — Variable name to expression mappings
  • context — Optional context; enables default-value inlining for scoped references (see note above)

Returns

New expression with substitutions applied (immutable)

Source

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)

From docs/api/examples/typescript/expressions.ts

Python

substitute(expr: Expr, bindings: dict[str, Expr]) -> Expr

Recursively substitute variables in an expression with their bindings.

Parameters

  • expr (Expr) — Expression to perform substitutions on
  • bindings (dict[str, Expr]) — Dictionary mapping variable names to replacement expressions

Returns

  • Expr — Expression with variables substituted

Source

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"}

From docs/api/examples/python/expressions.py

Rust

pub fn substitute(expr: &Expr, substitutions: &HashMap<String, Expr>) -> Expr

Substitute variables in an expression, returning a new expression.

Traverses the full expression tree — including aggregate/faq bodies, filter predicates, integral bounds, makearray values, and table_lookup axes — and preserves all operator-node metadata.

Source · Rust docs

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");

From docs/api/examples/rust/expressions.rs

Go

func Substitute(expr Expression, bindings map[string]Expression) (Expression, error)

Substitute performs variable substitution in expressions. expr: the expression to substitute into bindings: map from variable names to replacement expressions

Substitution is SINGLE-PASS (non-transitive), per the normative contract in CONFORMANCE_SPEC.md §2.2.3 rule 1: a binding’s replacement is inserted verbatim and is NOT itself re-substituted. Given {x -> y, y -> x}, substituting “x” yields “y”. This is what guarantees termination for self-referential and mutually-referential binding sets without any cycle detection, and it is what makes a binding map usable as a simultaneous RENAME map (edit.go’s renameRawExpr): {a -> b, b -> c} renames a to b, not to c.

The error return is retained for signature stability across the package’s substitution family; single-pass substitution over a decoded expression has no failure mode of its own.

Source · Go docs

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)

From docs/api/examples/go/expressions_test.go