1- export PolyForm, simplify_fractions
1+ export PolyForm, simplify_fractions, quick_cancel
22using Bijections
33using DynamicPolynomials: PolyVar
44
@@ -230,10 +230,10 @@ expand(expr) = PolyForm(expr, Fs=Union{typeof(+), typeof(*), typeof(^)}, recurse
230230
231231function polyform_factors (d:: Div , pvar2sym, sym2term)
232232 make (xs) = map (xs) do x
233- if x isa Pow && arguments (x)[ 2 ] isa Integer && arguments (x)[ 2 ] > 0
233+ if x isa Pow && x . base isa Integer && x . exp > 0
234234 # here we do want to recurse one level, that's why it's wrong to just
235235 # use Fs = Union{typeof(+), typeof(*)} here.
236- Pow (PolyForm (arguments (x)[ 1 ] , pvar2sym, sym2term), arguments (x)[ 2 ] )
236+ Pow (PolyForm (x . base , pvar2sym, sym2term), x . exp )
237237 else
238238 PolyForm (x, pvar2sym, sym2term)
239239 end
@@ -244,14 +244,14 @@ end
244244
245245_mul (xs... ) = all (isempty, xs) ? 1 : * (Iterators. flatten (xs)... )
246246
247- function simplify_fractions (d:: Div )
247+ function simplify_div (d:: Div )
248248 d. simplified && return d
249249 ns, ds = polyform_factors (d, get_pvar2sym (), get_sym2term ())
250250 ns, ds = rm_gcds (ns, ds)
251251 if all (_isone, ds)
252252 return isempty (ns) ? 1 : simplify_fractions (_mul (ns))
253253 else
254- return Div (simplify_fractions (_mul (ns)), simplify_fractions (_mul (ds)), true )
254+ Div (simplify_fractions (_mul (ns)), simplify_fractions (_mul (ds)), true )
255255 end
256256end
257257
@@ -269,12 +269,26 @@ Find `Div` nodes and simplify them by cancelling a set of factors of numerators
269269and denominators. It may leave some expressions in `PolyForm` format.
270270"""
271271function simplify_fractions (x)
272+ x = Postwalk (quick_cancel)(x)
273+
274+ ! needs_div_rules (x) && return x
275+
272276 isdiv (x) = x isa Div
273277
274- rules = [@acrule ~ a:: isdiv + ~ b:: isdiv => add_divs (~ a,~ b)
275- @rule ~ x:: isdiv => simplify_fractions (~ x)]
278+ rules = [@rule ~ x:: isdiv => simplify_div (~ x)
279+ @acrule ~ a:: isdiv + ~ b:: isdiv => add_divs (~ a,~ b)]
280+
281+ Fixpoint (Postwalk (Chain (rules)))(x)
282+ end
283+
284+ function needs_div_rules (x)
285+ (x isa Div && ! (x. num isa Number) && ! (x. den isa Number)) ||
286+ (istree (x) && operation (x) === (+ ) && count (has_div, unsorted_arguments (x)) > 1 ) ||
287+ (istree (x) && any (needs_div_rules, unsorted_arguments (x)))
288+ end
276289
277- Prewalk (RestartedChain (rules))(x)
290+ function has_div (x)
291+ return x isa Div || (istree (x) && any (has_div, unsorted_arguments (x)))
278292end
279293
280294flatten_pows (xs) = map (xs) do x
@@ -289,6 +303,110 @@ const MaybeGcd = Union{PolyForm, MP.AbstractPolynomialLike, Integer}
289303_gcd (x:: MaybeGcd , y:: MaybeGcd ) = (coefftype (x) <: Complex || coefftype (y) <: Complex ) ? 1 : gcd (x, y)
290304_gcd (x, y) = 1
291305
306+
307+ """
308+ quick_cancel(d::Div)
309+
310+ Cancel out matching factors from numerator and denominator.
311+ This is not as effective as `simplify_fractions`, for example,
312+ it wouldn't simplify `(x^2 + 15 - 8x) / (x - 5)` to `(x - 3)`.
313+ But it will simplify `(x - 5)^2*(x - 3) / (x - 5)` to `(x - 5)*(x - 3)`.
314+ Has optimized processes for `Mul` and `Pow` terms.
315+ """
316+ quick_cancel (d:: Div ) = Div {symtype(d)} (quick_cancel (d. num, d. den)... )
317+
318+ quick_cancel (x) = x
319+
320+ quick_cancel (x, y) = isequal (x, y) ? (1 ,1 ) : (x, y)
321+
322+ function quick_cancel (x:: Pow , y)
323+ x. exp isa Number || return (x, y)
324+ isequal (x. base, y) && x. exp >= 1 ? (Pow {symtype(x)} (x. base, x. exp - 1 ),1 ) : (x, y)
325+ end
326+
327+ quick_cancel (y, x:: Pow ) = reverse (quick_cancel (x,y))
328+
329+ function quick_cancel (x:: Pow , y:: Pow )
330+ if isequal (x. base, y. base)
331+ ! (x. exp isa Number && y. exp isa Number) && return (x, y)
332+ if x. exp > y. exp
333+ return Pow {symtype(x)} (x. base, x. exp- y. exp), 1
334+ elseif x. exp == y. exp
335+ return 1 , 1
336+ else # x.exp < y.exp
337+ return 1 , Pow {symtype(y)} (y. base, y. exp- x. exp)
338+ end
339+ end
340+ return x, y
341+ end
342+
343+ function quick_cancel (x:: Mul , y)
344+ if haskey (x. dict, y) && x. dict[y] >= 1
345+ d = copy (x. dict)
346+ if d[y] > 1
347+ d[y] -= 1
348+ elseif d[y] == 1
349+ delete! (d, y)
350+ else
351+ error (" Can't reach" )
352+ end
353+
354+ return Mul (symtype (x), x. coeff, d), 1
355+ else
356+ return x, y
357+ end
358+ end
359+
360+ function quick_cancel (x:: Mul , y:: Pow )
361+ y. exp isa Number || return (x, y)
362+ if haskey (x. dict, y. base)
363+ d = copy (x. dict)
364+ if x. dict[y. base] > y. exp
365+ d[y. base] -= y. exp
366+ den = 1
367+ elseif x. dict[y. base] == y. exp
368+ delete! (d, y. base)
369+ den = 1
370+ else
371+ den = Pow {symtype(y)} (y. base, y. exp- d[y. base])
372+ delete! (d, y. base)
373+ end
374+ return Mul (symtype (x), x. coeff, d), den
375+ else
376+ return x, y
377+ end
378+ end
379+
380+ quick_cancel (x:: Pow , y:: Mul ) = reverse (quick_cancel (y,x))
381+
382+ quick_cancel (y, x:: Mul ) = reverse (quick_cancel (x,y))
383+
384+ function quick_cancel (x:: Mul , y:: Mul )
385+ num_dict, den_dict = _merge_div (x. dict, y. dict)
386+ Mul (symtype (x), x. coeff, num_dict), Mul (symtype (y), y. coeff, den_dict)
387+ end
388+
389+ function _merge_div (ndict, ddict)
390+ num = copy (ndict)
391+ den = copy (ddict)
392+ for (k, v) in den
393+ if haskey (num, k)
394+ nk = num[k]
395+ if nk > v
396+ num[k] -= v
397+ delete! (den, k)
398+ elseif nk == v
399+ delete! (num, k)
400+ delete! (den, k)
401+ else
402+ den[k] -= nk
403+ delete! (num, k)
404+ end
405+ end
406+ end
407+ num, den
408+ end
409+
292410function rm_gcds (ns, ds)
293411 ns = flatten_pows (ns)
294412 ds = flatten_pows (ds)
0 commit comments