1
1
using Symbolics
2
2
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 )
4
6
rhs = Vector {Any} ([0 ])
5
7
original_lhs = deepcopy (lhs)
6
8
lhs = unwrap (lhs)
@@ -72,12 +74,21 @@ function isolate(lhs, var; warns=true, conditions=[])
72
74
power = args[2 ]
73
75
new_roots = []
74
76
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
81
92
end
82
93
end
83
94
rhs = []
@@ -90,57 +101,23 @@ function isolate(lhs, var; warns=true, conditions=[])
90
101
lhs = args[2 ]
91
102
rhs = map (sol -> term (/ , term (slog, sol), term (slog, args[1 ])), rhs)
92
103
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)
105
105
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
144
121
end
145
122
146
123
lhs = simplify (lhs)
@@ -149,7 +126,7 @@ function isolate(lhs, var; warns=true, conditions=[])
149
126
return rhs, conditions
150
127
end
151
128
152
- function attract (lhs, var; warns = true )
129
+ function attract (lhs, var; warns = true , complex_roots = true , periodic_roots = true )
153
130
if n_func_occ (simplify (lhs), var) <= n_func_occ (lhs, var)
154
131
lhs = simplify (lhs)
155
132
end
@@ -164,7 +141,9 @@ function attract(lhs, var; warns = true)
164
141
end
165
142
lhs = attract_trig (lhs, var)
166
143
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
168
147
169
148
lhs, sub = turn_to_poly (lhs, var)
170
149
@@ -182,12 +161,12 @@ function attract(lhs, var; warns = true)
182
161
new_var = collect (keys (sub))[1 ]
183
162
new_var_val = collect (values (sub))[1 ]
184
163
185
- roots, new_conds = isolate (lhs, new_var, warns = warns)
164
+ roots, new_conds = isolate (lhs, new_var; warns = warns, complex_roots, periodic_roots )
186
165
append! (conditions, new_conds)
187
166
new_roots = []
188
167
189
168
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 )
191
170
append! (conditions, new_conds)
192
171
push! (new_roots, new_sol)
193
172
end
@@ -197,7 +176,7 @@ function attract(lhs, var; warns = true)
197
176
end
198
177
199
178
"""
200
- ia_solve(lhs, var)
179
+ ia_solve(lhs, var; kwargs... )
201
180
This function attempts to solve transcendental functions by first checking
202
181
the "smart" number of occurrences in the input LHS. By smart here we mean
203
182
that 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
226
205
- lhs: a Num/SymbolicUtils.BasicSymbolic
227
206
- var: variable to solve for.
228
207
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
+
229
215
# Examples
230
216
```jldoctest
231
217
julia> solve(a*x^b + c, x)
@@ -256,20 +242,30 @@ julia> RootFinding.ia_solve(expr, x)
256
242
-2 + π*2var"##230" + asin((1//2)*(-1 + RootFinding.ssqrt(-39)))
257
243
-2 + π*2var"##234" + asin((1//2)*(-1 - RootFinding.ssqrt(-39)))
258
244
```
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
+
259
255
# References
260
256
[^1]: [R. W. Hamming, Coding and Information Theory, ScienceDirect, 1980](https://www.sciencedirect.com/science/article/pii/S0747717189800070).
261
257
"""
262
- function ia_solve (lhs, var; warns = true )
258
+ function ia_solve (lhs, var; warns = true , complex_roots = true , periodic_roots = true )
263
259
nx = n_func_occ (lhs, var)
264
260
sols = []
265
261
conditions = []
266
262
if nx == 0
267
263
warns && @warn (" Var not present in given expression" )
268
264
return []
269
265
elseif nx == 1
270
- sols, conditions = isolate (lhs, var, warns = warns)
266
+ sols, conditions = isolate (lhs, var; warns = warns, complex_roots, periodic_roots )
271
267
elseif nx > 1
272
- sols, conditions = attract (lhs, var, warns = warns)
268
+ sols, conditions = attract (lhs, var; warns = warns, complex_roots, periodic_roots )
273
269
end
274
270
275
271
isequal (sols, nothing ) && return nothing
0 commit comments