Speed up lambdifying#
Note
Since #374, expressions are lambdified with common sub-expressions. This should already reduce lambdification time significantly and also results in faster computational functions.
Split expression#
Lambdifying a SymPy expression can take rather long when an expression is complicated. Fortunately, TensorWaves offers a way to speed up the lambdify process. The idea is to split up an an expression into sub-expressions, separate those separately, and then recombining them. Let’s illustrate that idea with the following simplified example:
x, y, z = sp.symbols("x:z")
expr = x**z + 2 * y + sp.log(y * z)
expr
This expression can be represented in a tree of mathematical operations.
The function split_expression() can now be used to split up this expression tree into a ‘top expression’ plus definitions for each of the sub-expressions into which it was split:
top_expr, sub_expressions = split_expression(expr, max_complexity=3)
top_expr
sub_expressions
{f0: x**z, f1: 2*y, f2: log(y*z)}
The original expression can easily be reconstructed with subs() or xreplace():
top_expr.xreplace(sub_expressions)
Each of the expression trees are now smaller than the original:
| f0 | f1 | f2 |
|---|---|---|
Fast lambdify#
Generally, the lambdify time scales exponentially with the size of an expression tree. With larger expression trees, it’s therefore much faster to lambdify these sub-expressions separately and to recombine them. TensorWaves offers a function that does this for you: fast_lambdify(). We’ll use an HelicityModel to illustrate this:
reaction = qrules.generate_transitions(
initial_state=("J/psi(1S)", [+1]),
final_state=["gamma", "pi0", "pi0"],
allowed_intermediate_particles=["f(0)"],
)
model_builder = ampform.get_builder(reaction)
for name in reaction.get_intermediate_particles().names:
model_builder.dynamics.assign(name, create_relativistic_breit_wigner_with_ff)
model = model_builder.formulate()
%%time
split_function = fast_lambdify(
expression,
sorted_symbols, # ty:ignore[invalid-argument-type]
backend="numpy",
max_complexity=100,
)
CPU times: user 330 ms, sys: 3.22 ms, total: 333 ms
Wall time: 332 ms
%%time
normal_function = sp.lambdify(sorted_symbols, expression)
CPU times: user 2.41 s, sys: 8.84 ms, total: 2.42 s
Wall time: 2.32 s
Specifying complexity#
When creating a parametrized function, we use the create_parametrized_function() function. By default, this internally calls SymPy’s own lambdify() function. But if you specify its max_complexity argument, create_parametrized_function() uses TensorWaves’s fast_lambdify().
%%time
function = create_parametrized_function(
expression=model.expression.doit(),
parameters=model.parameter_defaults,
backend="numpy",
max_complexity=100,
)
CPU times: user 736 ms, sys: 6.02 ms, total: 742 ms
Wall time: 739 ms