Set as zero low values in symbolic expressions

Hello everyone,
I am seeking a way to simplify a symbolic expression setting as zero floating point numbers that are below a certain threshold.
For example the expression ‘1e-17*x + y’ should return ‘y’.
I tried to use the @rule macros without success, here is a simple coding example:

import Symbolics
import SymbolicUtils
using Symbolics
using SymbolicUtils.Rewriters

function zero_fun(term)
	threshold = 1e-15
	if isa(term,AbstractFloat) && abs(term) < threshold
		return true
	end
	return false
end

rule_zero = @rule ~x => 0

rw_zero = PassThrough(If(zero_fun, rule_zero))

# --- test --- #
@variables y, z
x = 1e-16
eq = (x*y+z)^2
# prune_terms(x, 1e-15)
rw_zero(x) # ok, returns '0'
rw_zero(x*y) # not ok, returns '1e-17y'
rw_zero(x*y + z) # not ok, returns '1e-17y+z'
rw_zero(eq) # same
rewriter_zero = Postwalk(rw_zero) # tried Postwalk
rewriter_zero(eq) # not ok

The code on ‘rw_zero(x*y)’ didn’t substitute as expected and returns ‘1e-17y’.

I didn’t understand how to let the rewriter elaborate on each argument of the expression.

Welcome to the forum @GiorgioSimonini :wave:

I actually had the same issue right now (I wanted to test whether I implemented an algorithm correctly using some symbolic variables, but had some leftover floats of size ~machine precision in there) and found your post. It might be too late for you, but maybe it helps someone else looking for a solution.

I think your rule is correct in principle (the combination of Prewalk/Postwalk and PassThrough), but there is a little pitfall when using the symbolic types from the package.

As described here, one can use the rules directly on a symbolic expression, but only if they involve the correct low-level type of symbols (e.g. created with @syms y z). The rule doesn’t work directly on the variables of type Num which are created with @variables. For that, we need the higher-level simplify function, which is documented here.

Here’s an example based on what you wrote (note that the condition can be written directly in the rule, it’s just a bit shorter but should be equivalent to your code)

using Symbolics
using Symbolics.SymbolicUtils.Rewriters

rule_too_small = @rule ~x::(xs -> xs isa AbstractFloat && abs(xs) < 1e-15) => 0
rule = Prewalk(PassThrough(rule_too_small))

@variables y, z
x = 1e-16
eq = (x*y+z)^2

rule(eq) # still returns (1.0e-16y + z)^2 since y and z are `Num`s

simplify(eq; rewriter=rule) # returns z^2

Hope that helps!

EDIT: I forgot to wrap the float in an abs just to make sure that it doesn’t replace negative values.

1 Like

Hi @Sevi, thank you for your reply.
I actually solved my problem by manually controlling the float values used to form the symbolic equation, so I haven’t discovered a solution before your comment.
Just one question(maybe two): Is the simplify function using something other than the rewrites to return the desired expression? In my case, if the symbolic expression is large, the computational complexity will be very high. In that case, is it better to directly use rewrites on @syms entities?

1 Like

I don’t know about the implementation details, but from the description it sounds like simplify iterates over the expression many times, trying to apply the given rewrite-rules until the expression doesn’t change anymore.
I have found that this can indeed take quite long for large expressions, but for my use case (big polynomials with around O(10)-O(100) different terms/variables) it’s still fast enough after the first compilation. When in doubt, best try it out :slight_smile:

But I think applying simplify only with your replacement rule of too small floating point values should be relatively fast, since it only goes through all the terms in the expression, checks if it can replace them, and does so if necessary. After this is applied, there should be nothing happening if it iterates again through all the terms, so it should stop after the first pass (given that expand=false and probably simplify_fractions=false).

It may or may not be dangerous though to replace small numbers by zeros when you have complicates expressions. E.g. if your term contains very big and very small numbers it could matter if you first simplify “normally” (expanding parentheses, etc.) and then replace small numbers or vice versa.

I don’t know if there is another (simple) way to directly rewrite the terms without simplify.