From 428284cb2ce3cb27316cc4f8200bb74c666a0056 Mon Sep 17 00:00:00 2001 From: brianguenter <1brianguenter@gmail.com> Date: Tue, 17 Sep 2024 16:18:47 -0700 Subject: [PATCH] fixed conditional test that was using ifelse instead of if_else added 2 new methods for ifelse so it can take inputs that are Real instead of just Node updated documentation --- docs/src/index.md | 67 +++++++++++++++++++++++++++++------------------ src/Methods.jl | 2 ++ test/FDTests.jl | 4 +-- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 4b75b15a..b9786cf0 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -14,33 +14,7 @@ For f:ℝⁿ->ℝᵐ with n,m large **FD** may have better performance than conv **FD** may take much less time to compute symbolic derivatives than Symbolics.jl even in the ℝ¹->ℝ¹ case. The executables generated by **FD** may also be much faster (see [Symbolic Processing](@ref)). -As of version 0.4.1 **FD** allows you to create expressions with conditionals: -```julia - -julia> @variables x y -y -julia> f = ifelse(x a = make_function([f],[x,y]) - -julia> a(1.0,2.0) -1-element Vector{Float64}: - 1.0 - -julia> a(2.0,1.0) -1-element Vector{Float64}: - 2.0 -``` -Howver, you cannot yet compute derivatives of expressions that contain conditionals: -```julia -julia> jacobian([f],[x,y]) -ERROR: Your expression contained ifelse. FastDifferentiation does not yet support differentiation through ifelse or any of these conditionals (max, min, copysign, &, |, xor, <, >, <=, >=, !=, ==, signbit, isreal, iszero, isfinite, isnan, isinf, isinteger, !) -``` -This may be a breaking change for some users. In previous versions this threw an the expression `x==y` returned a `Bool`. Some data structures, such as `Dict` use `==` by default to determine if two entries are the same. This will no longer work since `x==y` will now return an expression graph. Use an `IDict` instead since this uses `===`. - -A future PR will add support for differentiating through conditionals. @@ -66,6 +40,47 @@ This is **beta** software being modified on a daily basis. Expect bugs and frequ ## Notes about special derivatives The derivative of `|u|` is `u/|u|` which is NaN when `u==0`. This is not a bug. The derivative of the absolute value function is undefined at 0 and the way **FD** signals this is by returning NaN. +## Conditionals + +As of version 0.4.1 **FD** allows you to create expressions with conditionals using either the builtin `ifelse` function or a new function `if_else`. `ifelse` will evaluate both inputs. By contrast `if_else` has the semantics of `if...else...end`; only the true or false branch will be executed. This is useful when your conditional is used to prevent exceptions because of illegal input values: +```julia +julia> f = if_else(x<0,NaN,sqrt(x)) +(if_else (x < 0) NaN sqrt(x)) + +julia> g = make_function([f],[x]) + + +julia> g([-1]) +1-element Vector{Float64}: + NaN + +julia> g([2.0]) +1-element Vector{Float64}: + 1.4142135623730951 +end +``` +In this case you wouldn't want to use `ifelse` because it evaluates both the true and false branches and causes a runtime exception: +```julia +julia> f = ifelse(x<0,NaN,sqrt(x)) +(ifelse (x < 0) NaN sqrt(x)) + +julia> g = make_function([f],[x]) +... + +julia> g([-1]) +ERROR: DomainError with -1.0: +sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). +``` + +Howver, you cannot yet compute derivatives of expressions that contain conditionals: +```julia +julia> jacobian([f],[x,y]) +ERROR: Your expression contained ifelse. FastDifferentiation does not yet support differentiation through ifelse or any of these conditionals (max, min, copysign, &, |, xor, <, >, <=, >=, !=, ==, signbit, isreal, iszero, isfinite, isnan, isinf, isinteger, !) +``` +This may be a breaking change for some users. In previous versions the expression `x==y` returned a `Bool`. Some data structures, such as `Dict` use `==` by default to determine if two entries are the same. This will no longer work since `x==y` will now return an expression graph. Use an `IdDict` instead since this uses `===`. + +A future PR will add support for differentiating through conditionals. + diff --git a/src/Methods.jl b/src/Methods.jl index 86d9842a..8708b412 100644 --- a/src/Methods.jl +++ b/src/Methods.jl @@ -85,6 +85,8 @@ end macro number_methods(T, rhs1, rhs2, options=nothing) #special case for ifelse because it takes three arguments eval(:(Base.ifelse(a::$T, b::$T, c::$T) = simplify_check_cache(Base.ifelse, a, b, c))) + eval(:(Base.ifelse(a::$T, b::$Real, c::$T) = simplify_check_cache(Base.ifelse, a, Node(b), c))) + eval(:(Base.ifelse(a::$T, b::$T, c::$Real) = simplify_check_cache(Base.ifelse, a, b, Node(c)))) number_methods(T, rhs1, rhs2, options) |> esc end diff --git a/test/FDTests.jl b/test/FDTests.jl index 8cb773da..05bec783 100644 --- a/test/FDTests.jl +++ b/test/FDTests.jl @@ -2119,8 +2119,8 @@ end #conditional expr = x < y - f = ifelse(expr, x, y) - @test ==(FastDifferentiation.value(f), ifelse) + f = if_else(expr, x, y) + @test ==(FastDifferentiation.value(f), if_else) @test ===(FastDifferentiation.children(f)[1], expr) @test ===(FastDifferentiation.children(f)[2], x) @test ===(FastDifferentiation.children(f)[3], y)