11using Symbolics
22
3- function isolate (lhs, var; warns= true , conditions= [])
3+ const SAFE_ALTERNATIVES = Dict (log => slog, sqrt => ssqrt, cbrt => scbrt)
4+
5+ function isolate (lhs, var; warns= true , conditions= [], complex_roots = true , periodic_roots = true )
46 rhs = Vector {Any} ([0 ])
57 original_lhs = deepcopy (lhs)
68 lhs = unwrap (lhs)
@@ -72,12 +74,21 @@ function isolate(lhs, var; warns=true, conditions=[])
7274 power = args[2 ]
7375 new_roots = []
7476
75- for i in eachindex (rhs)
76- for k in 0 : (args[2 ] - 1 )
77- r = wrap (term (^ , rhs[i], (1 // power)))
78- c = wrap (term (* , 2 * (k), pi )) * im / power
79- root = r * Base. MathConstants. e^ c
80- push! (new_roots, root)
77+ if complex_roots
78+ for i in eachindex (rhs)
79+ for k in 0 : (args[2 ] - 1 )
80+ r = term (^ , rhs[i], (1 // power))
81+ c = term (* , 2 * (k), pi ) * im / power
82+ root = r * Base. MathConstants. e^ c
83+ push! (new_roots, root)
84+ end
85+ end
86+ else
87+ for i in eachindex (rhs)
88+ push! (new_roots, term (^ , rhs[i], (1 // power)))
89+ if iseven (power)
90+ push! (new_roots, term (- , new_roots[end ]))
91+ end
8192 end
8293 end
8394 rhs = []
@@ -90,57 +101,23 @@ function isolate(lhs, var; warns=true, conditions=[])
90101 lhs = args[2 ]
91102 rhs = map (sol -> term (/ , term (slog, sol), term (slog, args[1 ])), rhs)
92103 end
93-
94- elseif oper === (log) || oper === (slog)
95- lhs = args[1 ]
96- rhs = map (sol -> term (^ , Base. MathConstants. e, sol), rhs)
97- push! (conditions, (args[1 ], > ))
98-
99- elseif oper === (log2)
100- lhs = args[1 ]
101- rhs = map (sol -> term (^ , 2 , sol), rhs)
102- push! (conditions, (args[1 ], > ))
103-
104- elseif oper === (log10)
104+ elseif has_left_inverse (oper)
105105 lhs = args[1 ]
106- rhs = map (sol -> term (^ , 10 , sol), rhs)
107- push! (conditions, (args[1 ], > ))
108-
109- elseif oper === (sqrt)
110- lhs = args[1 ]
111- append! (conditions, [(r, >= ) for r in rhs])
112- rhs = map (sol -> term (^ , sol, 2 ), rhs)
113-
114- elseif oper === (cbrt)
115- lhs = args[1 ]
116- rhs = map (sol -> term (^ , sol, 3 ), rhs)
117-
118- elseif oper === (sin) || oper === (cos) || oper === (tan)
119- rev_oper = Dict (sin => asin, cos => acos, tan => atan)
120- lhs = args[1 ]
121- # make this global somehow so the user doesnt need to declare it on his own
122- new_var = gensym ()
123- new_var = (@variables $ new_var)[1 ]
124- rhs = map (
125- sol -> term (rev_oper[oper], sol) +
126- term (* , Base. MathConstants. pi , new_var),
127- rhs)
128- @info string (new_var) * " ϵ" * " Ζ"
129-
130- elseif oper === (asin)
131- lhs = args[1 ]
132- rhs = map (sol -> term (sin, sol), rhs)
133-
134- elseif oper === (acos)
135- lhs = args[1 ]
136- rhs = map (sol -> term (cos, sol), rhs)
137-
138- elseif oper === (atan)
139- lhs = args[1 ]
140- rhs = map (sol -> term (tan, sol), rhs)
141- elseif oper === (exp)
142- lhs = args[1 ]
143- rhs = map (sol -> term (slog, sol), rhs)
106+ ia_conditions! (oper, lhs, rhs, conditions)
107+ invop = left_inverse (oper)
108+ invop = get (SAFE_ALTERNATIVES, invop, invop)
109+ if is_periodic (oper) && periodic_roots
110+ new_var = gensym ()
111+ new_var = (@variables $ new_var)[1 ]
112+ period = fundamental_period (oper)
113+ rhs = map (
114+ sol -> term (invop, sol) +
115+ term (* , period, new_var),
116+ rhs)
117+ @info string (new_var) * " ϵ" * " Ζ"
118+ else
119+ rhs = map (sol -> term (invop, sol), rhs)
120+ end
144121 end
145122
146123 lhs = simplify (lhs)
@@ -149,7 +126,7 @@ function isolate(lhs, var; warns=true, conditions=[])
149126 return rhs, conditions
150127end
151128
152- function attract (lhs, var; warns = true )
129+ function attract (lhs, var; warns = true , complex_roots = true , periodic_roots = true )
153130 if n_func_occ (simplify (lhs), var) <= n_func_occ (lhs, var)
154131 lhs = simplify (lhs)
155132 end
@@ -164,7 +141,9 @@ function attract(lhs, var; warns = true)
164141 end
165142 lhs = attract_trig (lhs, var)
166143
167- n_func_occ (lhs, var) == 1 && return isolate (lhs, var, warns = warns, conditions= conditions)
144+ if n_func_occ (lhs, var) == 1
145+ return isolate (lhs, var; warns, conditions, complex_roots, periodic_roots)
146+ end
168147
169148 lhs, sub = turn_to_poly (lhs, var)
170149
@@ -182,12 +161,12 @@ function attract(lhs, var; warns = true)
182161 new_var = collect (keys (sub))[1 ]
183162 new_var_val = collect (values (sub))[1 ]
184163
185- roots, new_conds = isolate (lhs, new_var, warns = warns)
164+ roots, new_conds = isolate (lhs, new_var; warns = warns, complex_roots, periodic_roots )
186165 append! (conditions, new_conds)
187166 new_roots = []
188167
189168 for root in roots
190- new_sol, new_conds = isolate (new_var_val - root, var, warns = warns)
169+ new_sol, new_conds = isolate (new_var_val - root, var; warns = warns, complex_roots, periodic_roots )
191170 append! (conditions, new_conds)
192171 push! (new_roots, new_sol)
193172 end
@@ -197,7 +176,7 @@ function attract(lhs, var; warns = true)
197176end
198177
199178"""
200- ia_solve(lhs, var)
179+ ia_solve(lhs, var; kwargs... )
201180This function attempts to solve transcendental functions by first checking
202181the "smart" number of occurrences in the input LHS. By smart here we mean
203182that polynomials are counted as 1 occurrence. for example `x^2 + 2x` is 1
@@ -226,6 +205,13 @@ we throw an error to tell the user that this is currently unsolvable by our cove
226205- lhs: a Num/SymbolicUtils.BasicSymbolic
227206- var: variable to solve for.
228207
208+ # Keyword arguments
209+ - `warns = true`: Whether to emit warnings for unsolvable expressions.
210+ - `complex_roots = true`: Whether to consider complex roots of `x ^ n ~ y`, where `n` is an integer.
211+ - `periodic_roots = true`: If `true`, isolate `f(x) ~ y` as `x ~ finv(y) + n * period` where
212+ `is_periodic(f) == true`, `finv = left_inverse(f)` and `period = fundamental_period(f)`. `n`
213+ is a new anonymous symbolic variable.
214+
229215# Examples
230216```jldoctest
231217julia> solve(a*x^b + c, x)
@@ -256,20 +242,30 @@ julia> RootFinding.ia_solve(expr, x)
256242 -2 + π*2var"##230" + asin((1//2)*(-1 + RootFinding.ssqrt(-39)))
257243 -2 + π*2var"##234" + asin((1//2)*(-1 - RootFinding.ssqrt(-39)))
258244```
245+
246+ All transcendental functions for which `left_inverse` is defined are supported.
247+ To enable `ia_solve` to handle custom transcendental functions, define an inverse or
248+ left inverse. If the function is periodic, `is_periodic` and `fundamental_period` must
249+ be defined. If the function imposes certain conditions on its input or output (for
250+ example, `log` requires that its input be positive) define `ia_conditions!`.
251+
252+ See also: [`left_inverse`](@ref), [`inverse`](@ref), [`is_periodic`](@ref),
253+ [`fundamental_period`](@ref), [`ia_conditions!`](@ref).
254+
259255# References
260256[^1]: [R. W. Hamming, Coding and Information Theory, ScienceDirect, 1980](https://www.sciencedirect.com/science/article/pii/S0747717189800070).
261257"""
262- function ia_solve (lhs, var; warns = true )
258+ function ia_solve (lhs, var; warns = true , complex_roots = true , periodic_roots = true )
263259 nx = n_func_occ (lhs, var)
264260 sols = []
265261 conditions = []
266262 if nx == 0
267263 warns && @warn (" Var not present in given expression" )
268264 return []
269265 elseif nx == 1
270- sols, conditions = isolate (lhs, var, warns = warns)
266+ sols, conditions = isolate (lhs, var; warns = warns, complex_roots, periodic_roots )
271267 elseif nx > 1
272- sols, conditions = attract (lhs, var, warns = warns)
268+ sols, conditions = attract (lhs, var; warns = warns, complex_roots, periodic_roots )
273269 end
274270
275271 isequal (sols, nothing ) && return nothing
0 commit comments