# Revision history [back]

I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely:

Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression

my_collect2((a+b)*x + x, x)


shall yield

(a+b+1) * x


Generally assume that fact is also any expression. If fact occures as common factor in two or more of the sums, fact should be factored out by the call my_collect2(expr, fact).

Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sum of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products.

More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's.

Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have

var('a b c x y z')
my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y)


I do not want to get

(a+b+2*c) * (x+y)


since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y).

It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the sums.

I give some examples:

sage: var('x1 y1 y2 y3 z1 z2')
sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1)


Expected: (y2 + y3) * x1*y1

sage: var('x1 y1 y2 y3 z1')
sage: mycollect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1  )), (x1+cos(y1))*z1)
Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1  ))

sage: mycollect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1)
Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)]


I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely:

Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression

my_collect2((a+b)*x + x, x)


shall yield

(a+b+1) * x


Generally assume that fact is also any expression. If fact occures as common factor in two or more of the sums, fact should be factored out by the call my_collect2(expr, fact).

Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sum sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products.

More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's.

Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have

var('a b c x y z')
my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y)


I do not want to get

(a+b+2*c) * (x+y)


since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y).

It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the sums.

I give some examples:

sage: var('x1 y1 y2 y3 z1 z2')
sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1)


Expected: (y2 + y3) * x1*y1

sage: var('x1 y1 y2 y3 z1')
sage: mycollect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1  )), (x1+cos(y1))*z1)
Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1  ))

sage: mycollect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1)
Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)]


I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely:

Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression

my_collect2((a+b)*x + x, x)


shall yield

(a+b+1) * x


Generally assume that fact is also any expression. If fact occures as common factor in two or more of the sums, fact should be factored out by the call my_collect2(expr, fact).

Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products.

More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's.

Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have

var('a b c x y z')
my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y)


I do not want to get

(a+b+2*c) * (x+y)


since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y).

It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the sums.products.

I give some examples:

sage: var('x1 y1 y2 y3 z1 z2')
sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1)


Expected: (y2 + y3) * x1*y1

sage: var('x1 y1 y2 y3 z1')
sage: mycollect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1  )), (x1+cos(y1))*z1)
Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1  ))

sage: mycollect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1)
Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)]


I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely:

Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression

my_collect2((a+b)*x + x, x)


shall yield

(a+b+1) * x


Generally assume that fact is also any expression. If fact occures as common factor in two or more of the sums, fact should be factored out by the call my_collect2(expr, fact).

Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products.

More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's.

Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have

var('a b c x y z')
my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y)


I do not want to get

(a+b+2*c) * (x+y)


since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y).

Further more, my_collect2 should also be callable with three arguments:

my_collect2(expr, fact, subs_fact)


should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g.

sage: var('x1 y1 y2 y3 z1 z2 x1y1')
sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1)


Expected: (y2 + y3) * x1y1

It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products.

I give some examples:

sage: var('x1 y1 y2 y3 z1 z2')
sage: mycollect2(x1*y1*y2 my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1)


Expected: (y2 + y3) * x1*y1

sage: var('x1 y1 y2 y3 z1')
sage: mycollect2(sin(exp( my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1  )), (x1+cos(y1))*z1)
Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1  ))

sage: mycollect2([z1*(x1+y1) my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1)
Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)]


I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely:

Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression

my_collect2((a+b)*x + x, x)


shall yield

(a+b+1) * x


Generally assume that fact is also any expression. If fact occures as common factor in two or more of the sums, products, fact should be factored out by the call my_collect2(expr, fact).

Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products.

More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's.

Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have

var('a b c x y z')
my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y)


I do not want to get

(a+b+2*c) * (x+y)


since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y).

Further more, my_collect2 should also be callable with three arguments:

my_collect2(expr, fact, subs_fact)


should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g.

sage: var('x1 y1 y2 y3 z1 z2 x1y1')
sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1)


Expected: (y2 + y3) * x1y1

It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products.

I give some examples:

sage: var('x1 y1 y2 y3 z1 z2')
sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1)


Expected: (y2 + y3) * x1*y1

sage: var('x1 y1 y2 y3 z1')
sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1  )), (x1+cos(y1))*z1)
Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1  ))

sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1)
Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)]


I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely:

Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression

my_collect2((a+b)*x + x, x)


shall yield

(a+b+1) * x


Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact).

Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products.

More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's.

Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have

var('a b c x y z')
my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y)


I do not want to get

(a+b+2*c) * (x+y)


since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y).

Further more, my_collect2 should also be callable with three arguments:

my_collect2(expr, fact, subs_fact)


should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g.

sage: var('x1 y1 y2 y3 z1 z2 x1y1')
sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1)


Expected: (y2 + y3) * x1y1

It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products.

I give some examples:

sage: var('x1 y1 y2 y3 z1 z2')
sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1)


Expected: (y2 + y3) * x1*y1

sage: var('x1 y1 y2 y3 z1')
sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1  )), (x1+cos(y1))*z1)
Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1  ))

sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1)
Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)]


Edit 10/04/2022:

Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is:

from sage.symbolic.operators import add_vararg, mul_vararg

def is_exact_positive_integer_multiple_of(x, test_multiple):
# returns None if x or test_multiple are not exact (containing floating point numbers).
# Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there
# is a positive integer n so that test_multiple == n * x. If there is no such integer or there
# is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists,
# is_exact_positive_integer_multiple_of(x, test_multiple) returns None.
# E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)))
# could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but
# it could also return None since sagemath may not be able to prove that this holds.
var('dummy')
if not (dummy + x).is_exact() or not (dummy + test_multiple).is_exact():
return None
x = (x + dummy).simplify_full() - dummy
test_multiple = (test_multiple + dummy).simplify_full() - dummy
if 0 == x:
return None
else:
if ((test_multiple / x).is_integer() or \
(x in CC and test_multiple in CC and QQbar(test_multiple / x).is_integer())) \
and test_multiple >= x:
return test_multiple / x
else:
return None

def is_ratio_gerater_equal_one(x, test):
# returns None if x or test are not exact (containing floating point numbers).
# Otherwise, returns test / x if sagemath can prove (by simplification, if necessary)
# that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1,
# is_ratio_gerater_equal_one(x, test) returns None
var('dummy')
if not (dummy + x).is_exact() or not (dummy + test).is_exact():
return None
x = (x + dummy).simplify_full() - dummy
test = (test + dummy).simplify_full() - dummy
if 0 == x:
return None
else:
if x.is_integer() and test.is_integer():
if (x > 0 and test > x-1) or (x < 0 and test < x+1):
# You may think that this if-interrogation is redundant due to the following
# interrogation for real x and test.
# But it seems that sagemath cannot infer that z >= 1 if you declare the following:
# var("y z")
# assume(y, "integer")
# assume(z, "integer", z>0)
# Without the preceding interrogation
#     is_ratio_gerater_equal_one(y, y+z-1)
# would yield False.
return test / x
else:
return None
elif test / x >= 1 or (x in CC and test in CC and QQbar(test / x) >= 1):
return test / x
else:
return None

def is_proven_integer(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool(x.is_integer() or (x in CC and QQbar(x).is_integer()))

def is_proven_nonnegative(x):
var('dummy x')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
if is_proven_real(x):
rr = True
elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \
(is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])):
rr = True
else:
rr = False
return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \
(x in CC and QQbar(x).is_real() and bool(QQbar(x) >= 0))

def is_proven_positive(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool((x.is_real() and x > 0) or (x in CC and QQbar(x) > 0))

def is_proven_real(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool(x.is_real() or (x in CC and QQbar(x).is_real())) or \
(x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \
(is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])))

def can_contract_consecutive_exponentiation(x, y, z):
# returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False.
return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z))

def try_concatenate_consecutive_exponentiations(expr):
if expr.operator() is not operator.pow:
return expr, 1
else:
base = expr.operands()[0]
expn = expr.operands()[1]
while base.operator() is operator.pow and \
can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn):
expn = base.operands()[1] * expn
base = base.operands()[0]
return base, expn

def can_expand_power_of_product(x, y, z):
# returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False.
return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z))

def remove_power_of_factor_from_product_or_power(expr, factor, exponent, max_exponent = False,
only_from_integer_powers = False):
if max_exponent:
return remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers)
else:
r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent,
only_from_integer_powers)
return r, None if r is None else 1

def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent, only_from_integer_powers):
# factor must contain at least one variable, and this variable must also be in expr.
# If expr == factor ** exponent, this function returns the pair 1, 1 .
# Otherwise: 'expr' must be a product or an exponentiation (power function).
# If 'factor' is a product or an exponentiation, look at all factors inside 'factor'
# that contain a variable (no constant factors), otherwise look at 'factor' as a whole. Under some conditions,
# remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values
#    expr / (factor ** exponent)   ,    nn
# otherwise, it returns None, None. These conditions are:
# --- if only_from_integer_powers==True:
#     all factor ???????????????????????????????????????????????????????????????????

if 0 == exponent or factor in CC or expr in CC:   # factor in CC: if factor is constant, i.e. if it contains no variables
return None, None
if factor.is_zero()  # this test should normally not be necessary due to above test factor in CC
return None, None

facop = factor.operator()
if facop is operator.pow:
factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent)
facop = factor.operator()

if facop == mul_vararg:
p = expr
intpow = None
for fac in factor.operands():
if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent):
return None, None
if fac in CC:  # if fac is constant, i.e. if it contains no variables
p /= fac ** exponent
else:
p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent,
only_from_integer_powers)
if p is None:
return None, None
if intpow is None:
intpow = nn
elif only_from_integer_powers and nn != intpow:
return None, None
return p,  nn if only_from_integer_powers else 0

op = expr.operator()
if op is None:
if expr == factor ** exponent:
return 1, 1 if only_from_integer_powers else 0
else:
return None, None
elif op is operator.pow and expr.operands()[0]==factor and \
(is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \
else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None:
# The second return value expr.operands()[1] / exponent is real (not complex, nonreal)
# because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one()
# condition.
return factor ** (expr.operands()[1] - exponent), \
expr.operands()[1] / exponent if only_from_integer_powers else 0
elif op == mul_vararg:
without = None
for i,fac in enumerate(expr.operands()):
if fac == factor ** exponent:
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without,  1 if only_from_integer_powers else 0
elif fac.operator() is operator.pow:
# x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiation.
base, expn = try_concatenate_consecutive_exponentiations(fac)
if base == factor and \
(is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \
else is_ratio_gerater_equal_one(exponent, expn)) is not None:
without = factor ** (expn - exponent)
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
# The second return value fac.operands()[1] / exponent is real (not complex, nonreal)
# because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one()
# condition.
return without,  fac.operands()[1] / exponent if only_from_integer_powers else 0
return None, None
else:
return None, None

def remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers):

if 0 == exponent or factor in CC or expr in CC:   # factor in CC: if factor is constant, i.e. if it contains no variables
return None, None

facop = factor.operator()
if facop is operator.pow:
factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent)
facop = factor.operator()

if facop == mul_vararg:
min_abs_exp = None
p = expr
for fac in factor.operands():
if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent):
return None, None
if fac in CC:  # if fac is constant, i.e. if it contains no variables
pass  # needn't divide by constant since p will be discarded and re-calculated below
else:
p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent,
only_from_integer_powers)
if p is None:
return None, None
if min_abs_exp is None or abs(nn) < min_abs_exp:
min_abs_exp = abs(nn)
p = expr
for fac in factor.operands():
if fac in CC:  # if fac is constant, i.e. if it contains no variables
p /= fac ** (min_abs_exp * exponent)
else:
p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac,
min_abs_exp * exponent,
only_from_integer_powers)
if p is None:
return None, None
return p, min_abs_exp
op = expr.operator()
if op is None:
if expr == factor ** exponent:
return 1, 1
else:
return None, None
elif op is operator.pow and expr.operands()[0]==factor and only_from_integer_powers and \
is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None:
# The second return ee is real (not complex, nonreal) because of the tested
# is_exact_positive_integer_multiple_of() condition.
ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1])
return 1, ee
elif op is operator.pow and expr.operands()[0]==factor and not only_from_integer_powers and \
is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None:
ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1])
# The second return ee is real (not complex, nonreal) because of the tested
# is_ratio_gerater_equal_one() condition.
return factor ** (expr.operands()[1] - exponent * ee), ee
elif op == mul_vararg:
without = None
for i,fac in enumerate(expr.operands()):
if fac == factor ** exponent:
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without, 1
elif fac.operator() is operator.pow:
# x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiation.
base, expn = try_concatenate_consecutive_exponentiations(fac)
if base == factor:
if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None:
ee = is_exact_positive_integer_multiple_of(exponent, expn)
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without, ee
elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None:
ee = is_ratio_gerater_equal_one(exponent, expn)
without = factor ** (expn - exponent * ee)
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
# The second return ee is real (not complex, nonreal)
# because of the tested is_ratio_gerater_equal_one() condition.
return without, ee
return None, None
else:
return None, None

def factor_out(expr, factor, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False,
replace_factor_by = None):

if type(expr) == list:
return [factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)
for ex in expr]
if type(expr) == tuple:
return tuple(factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)
for ex in expr)

if replace_factor_by is None:
replace_factor_by = factor

op = expr.operator()
if op is None or op != add_vararg:
if op is None or not recursive:
return expr
else:
return op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in expr.operands()])

summands = expr.operands()
factors_removed = []

if all_integer_powers:
not_containing = summands
while True:
fac_removed = []
summands = not_containing
max_exponent = None
for p in summands:
op = p.operator()
if op == mul_vararg or (op is operator.pow and p.operands()[0]==factor and p.operands()[1] >= 1):
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, True,
only_from_integer_powers)
if without_factor is not None:
if max_exponent is None or ee > max_exponent:
max_exponent = ee
if max_exponent is None:
break
not_containing = []  # must be after the preceeding if .. break statements
for p in summands:
op = p.operator()
if op == mul_vararg:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent,
False, only_from_integer_powers)
if without_factor is None:
if not recursive:
not_containing.append(p)
else:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
fac_removed.append(without_factor)
else:
fac_removed.append(
op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
elif op is operator.pow:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent,
False, only_from_integer_powers)
if without_factor is None:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
fac_removed.append(without_factor)
else:
fac_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
else:
if p == factor:
fac_removed.append(1)
else:
not_containing.append(factor_out(p, factor, recursive = recursive, all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by))
if 0==len(fac_removed):
break
else:
factors_removed.append( (fac_removed, max_exponent) )
return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing)

else:

not_containing = []
for p in summands:
op = p.operator()
if op == mul_vararg:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, False,
only_from_integer_powers)
if without_factor is None:
if not recursive:
not_containing.append(p)
else:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
factors_removed.append(without_factor)
else:
factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
elif op is operator.pow:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, all_integer_powers,
only_from_integer_powers)
if without_factor is None:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
factors_removed.append(without_factor)
else:
factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
else:
if p == factor:
factors_removed.append(1)
else:
not_containing.append(factor_out(p, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by))

return sum(factors_removed) * replace_factor_by + sum(not_containing)


I give some examples how to use it:

var('a b c d')
expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2)
factr = x
print("factor_out(", expr, ", ", factr, ")")
for allint in [False, True]:
for onlyfromint in [False, True]:
print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint))


The output is:

factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) ,  x )
allintpow =  False  onlyfromint =  False  result =  (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2)
allintpow =  False  onlyfromint =  True  result =  7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2)
allintpow =  True  onlyfromint =  False  result =  (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2)
allintpow =  True  onlyfromint =  True  result =  7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2)


I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely:

Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression

my_collect2((a+b)*x + x, x)


shall yield

(a+b+1) * x


Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact).

Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products.

More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's.

Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have

var('a b c x y z')
my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y)


I do not want to get

(a+b+2*c) * (x+y)


since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y).

Further more, my_collect2 should also be callable with three arguments:

my_collect2(expr, fact, subs_fact)


should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g.

sage: var('x1 y1 y2 y3 z1 z2 x1y1')
sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1)


Expected: (y2 + y3) * x1y1

It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products.

I give some examples:

sage: var('x1 y1 y2 y3 z1 z2')
sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1)


Expected: (y2 + y3) * x1*y1

sage: var('x1 y1 y2 y3 z1')
sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1  )), (x1+cos(y1))*z1)
Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1  ))

sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1)
Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)]


Edit 10/04/2022:

Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is:

from sage.symbolic.operators import add_vararg, mul_vararg

def is_exact_positive_integer_multiple_of(x, test_multiple):
# returns None if x or test_multiple are not exact (containing floating point numbers).
# Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there
# is a positive integer n so that test_multiple == n * x. If there is no such integer or there
# is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists,
# is_exact_positive_integer_multiple_of(x, test_multiple) returns None.
# E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)))
# could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but
# it could also return None since sagemath may not be able to prove that this holds.
var('dummy')
if not (dummy + x).is_exact() or not (dummy + test_multiple).is_exact():
return None
x = (x + dummy).simplify_full() - dummy
test_multiple = (test_multiple + dummy).simplify_full() - dummy
if 0 == x:
return None
else:
if ((test_multiple / x).is_integer() or \
(x in CC and test_multiple in CC and QQbar(test_multiple / x).is_integer())) \
and test_multiple >= x:
return test_multiple / x
else:
return None

def is_ratio_gerater_equal_one(x, test):
# returns None if x or test are not exact (containing floating point numbers).
# Otherwise, returns test / x if sagemath can prove (by simplification, if necessary)
# that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1,
# is_ratio_gerater_equal_one(x, test) returns None
var('dummy')
if not (dummy + x).is_exact() or not (dummy + test).is_exact():
return None
x = (x + dummy).simplify_full() - dummy
test = (test + dummy).simplify_full() - dummy
if 0 == x:
return None
else:
if x.is_integer() and test.is_integer():
if (x > 0 and test > x-1) or (x < 0 and test < x+1):
# You may think that this if-interrogation is redundant due to the following
# interrogation for real x and test.
# But it seems that sagemath cannot infer that z >= 1 if you declare the following:
# var("y z")
# assume(y, "integer")
# assume(z, "integer", z>0)
# Without the preceding interrogation
#     is_ratio_gerater_equal_one(y, y+z-1)
# would yield False.
return test / x
else:
return None
elif test / x >= 1 or (x in CC and test in CC and QQbar(test / x) >= 1):
return test / x
else:
return None

def is_proven_integer(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool(x.is_integer() or (x in CC and QQbar(x).is_integer()))

def is_proven_nonnegative(x):
var('dummy x')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
if is_proven_real(x):
rr = True
elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \
(is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])):
rr = True
else:
rr = False
return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \
(x in CC and QQbar(x).is_real() and bool(QQbar(x) >= 0))

def is_proven_positive(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool((x.is_real() and x > 0) or (x in CC and QQbar(x) > 0))

def is_proven_real(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool(x.is_real() or (x in CC and QQbar(x).is_real())) or \
(x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \
(is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])))

def can_contract_consecutive_exponentiation(x, y, z):
# returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False.
return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z))

def try_concatenate_consecutive_exponentiations(expr):
if expr.operator() is not operator.pow:
return expr, 1
else:
base = expr.operands()[0]
expn = expr.operands()[1]
while base.operator() is operator.pow and \
can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn):
expn = base.operands()[1] * expn
base = base.operands()[0]
return base, expn

def can_expand_power_of_product(x, y, z):
# returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False.
return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z))

def remove_power_of_factor_from_product_or_power(expr, factor, exponent, max_exponent = False,
only_from_integer_powers = False):
if max_exponent:
return remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers)
else:
r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent,
only_from_integer_powers)
return r, None if r is None else 1

def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent, only_from_integer_powers):
# factor must contain at least one variable, and this variable must also be in expr.
# If expr == factor ** exponent, this function returns the pair 1, 1 .
# Otherwise: 'expr' must be a product or an exponentiation (power function).
# If 'factor' is a product or an exponentiation, look at all factors inside 'factor'
# that contain a variable (no constant factors), otherwise look at 'factor' as a whole. Under some conditions,
# remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values
#    expr / (factor ** exponent)   ,    nn
# otherwise, it returns None, None. These conditions are:
# --- if only_from_integer_powers==True:
#     all factor ???????????????????????????????????????????????????????????????????

if 0 == exponent or factor in CC or expr in CC:   # factor in CC: if factor is constant, i.e. if it contains no variables
return None, None
if factor.is_zero()  # this test should normally not be necessary due to above test factor in CC
return None, None

facop = factor.operator()
if facop is operator.pow:
factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent)
facop = factor.operator()

if facop == mul_vararg:
p = expr
intpow = None
for fac in factor.operands():
if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent):
return None, None
if fac in CC:  # if fac is constant, i.e. if it contains no variables
p /= fac ** exponent
else:
p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent,
only_from_integer_powers)
if p is None:
return None, None
if intpow is None:
intpow = nn
elif only_from_integer_powers and nn != intpow:
return None, None
return p,  nn if only_from_integer_powers else 0

op = expr.operator()
if op is None:
if expr == factor ** exponent:
return 1, 1 if only_from_integer_powers else 0
else:
return None, None
elif op is operator.pow and expr.operands()[0]==factor and \
(is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \
else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None:
# The second return value expr.operands()[1] / exponent is real (not complex, nonreal)
# because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one()
# condition.
return factor ** (expr.operands()[1] - exponent), \
expr.operands()[1] / exponent if only_from_integer_powers else 0
elif op == mul_vararg:
without = None
for i,fac in enumerate(expr.operands()):
if fac == factor ** exponent:
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without,  1 if only_from_integer_powers else 0
elif fac.operator() is operator.pow:
# x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiation.
base, expn = try_concatenate_consecutive_exponentiations(fac)
if base == factor and \
(is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \
else is_ratio_gerater_equal_one(exponent, expn)) is not None:
without = factor ** (expn - exponent)
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
# The second return value fac.operands()[1] / exponent is real (not complex, nonreal)
# because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one()
# condition.
return without,  fac.operands()[1] / exponent if only_from_integer_powers else 0
return None, None
else:
return None, None

def remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers):

if 0 == exponent or factor in CC or expr in CC:   # factor in CC: if factor is constant, i.e. if it contains no variables
return None, None

facop = factor.operator()
if facop is operator.pow:
factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent)
facop = factor.operator()

if facop == mul_vararg:
min_abs_exp = None
p = expr
for fac in factor.operands():
if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent):
return None, None
if fac in CC:  # if fac is constant, i.e. if it contains no variables
pass  # needn't divide by constant since p will be discarded and re-calculated below
else:
p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent,
only_from_integer_powers)
if p is None:
return None, None
if min_abs_exp is None or abs(nn) < min_abs_exp:
min_abs_exp = abs(nn)
p = expr
for fac in factor.operands():
if fac in CC:  # if fac is constant, i.e. if it contains no variables
p /= fac ** (min_abs_exp * exponent)
else:
p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac,
min_abs_exp * exponent,
only_from_integer_powers)
if p is None:
return None, None
return p, min_abs_exp
op = expr.operator()
if op is None:
if expr == factor ** exponent:
return 1, 1
else:
return None, None
elif op is operator.pow and expr.operands()[0]==factor and only_from_integer_powers and \
is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None:
# The second return ee is real (not complex, nonreal) because of the tested
# is_exact_positive_integer_multiple_of() condition.
ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1])
return 1, ee
elif op is operator.pow and expr.operands()[0]==factor and not only_from_integer_powers and \
is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None:
ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1])
# The second return ee is real (not complex, nonreal) because of the tested
# is_ratio_gerater_equal_one() condition.
return factor ** (expr.operands()[1] - exponent * ee), ee
elif op == mul_vararg:
without = None
for i,fac in enumerate(expr.operands()):
if fac == factor ** exponent:
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without, 1
elif fac.operator() is operator.pow:
# x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiation.
base, expn = try_concatenate_consecutive_exponentiations(fac)
if base == factor:
if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None:
ee = is_exact_positive_integer_multiple_of(exponent, expn)
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without, ee
elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None:
ee = is_ratio_gerater_equal_one(exponent, expn)
without = factor ** (expn - exponent * ee)
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
# The second return ee is real (not complex, nonreal)
# because of the tested is_ratio_gerater_equal_one() condition.
return without, ee
return None, None
else:
return None, None

def factor_out(expr, factor, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False,
replace_factor_by = None):

if type(expr) == list:
return [factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)
for ex in expr]
if type(expr) == tuple:
return tuple(factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)
for ex in expr)

if replace_factor_by is None:
replace_factor_by = factor

op = expr.operator()
if op is None or op != add_vararg:
if op is None or not recursive:
return expr
else:
return op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in expr.operands()])

summands = expr.operands()
factors_removed = []

if all_integer_powers:
not_containing = summands
while True:
fac_removed = []
summands = not_containing
max_exponent = None
for p in summands:
op = p.operator()
if op == mul_vararg or (op is operator.pow and p.operands()[0]==factor and p.operands()[1] >= 1):
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, True,
only_from_integer_powers)
if without_factor is not None:
if max_exponent is None or ee > max_exponent:
max_exponent = ee
if max_exponent is None:
break
not_containing = []  # must be after the preceeding if .. break statements
for p in summands:
op = p.operator()
if op == mul_vararg:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent,
False, only_from_integer_powers)
if without_factor is None:
if not recursive:
not_containing.append(p)
else:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
fac_removed.append(without_factor)
else:
fac_removed.append(
op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
elif op is operator.pow:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent,
False, only_from_integer_powers)
if without_factor is None:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
fac_removed.append(without_factor)
else:
fac_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
else:
if p == factor:
fac_removed.append(1)
else:
not_containing.append(factor_out(p, factor, recursive = recursive, all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by))
if 0==len(fac_removed):
break
else:
factors_removed.append( (fac_removed, max_exponent) )
return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing)

else:

not_containing = []
for p in summands:
op = p.operator()
if op == mul_vararg:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, False,
only_from_integer_powers)
if without_factor is None:
if not recursive:
not_containing.append(p)
else:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
factors_removed.append(without_factor)
else:
factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
elif op is operator.pow:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, all_integer_powers,
only_from_integer_powers)
if without_factor is None:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
factors_removed.append(without_factor)
else:
factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
else:
if p == factor:
factors_removed.append(1)
else:
not_containing.append(factor_out(p, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by))

return sum(factors_removed) * replace_factor_by + sum(not_containing)


I give some examples how to use it:

var('a b c d')
expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2)
factr = x
print("factor_out(", expr, ", ", factr, ")")
for allint in [False, True]:
for onlyfromint in [False, True]:
print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint))


The output is:

factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) ,  x )
allintpow =  False  onlyfromint =  False  result =  (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2)
allintpow =  False  onlyfromint =  True  result =  7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2)
allintpow =  True  onlyfromint =  False  result =  (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2)
allintpow =  True  onlyfromint =  True  result =  7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2)


I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely:

Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression

my_collect2((a+b)*x + x, x)


shall yield

(a+b+1) * x


Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact).

Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products.

More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's.

Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have

var('a b c x y z')
my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y)


I do not want to get

(a+b+2*c) * (x+y)


since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y).

Further more, my_collect2 should also be callable with three arguments:

my_collect2(expr, fact, subs_fact)


should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g.

sage: var('x1 y1 y2 y3 z1 z2 x1y1')
sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1)


Expected: (y2 + y3) * x1y1

It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products.

I give some examples:

sage: var('x1 y1 y2 y3 z1 z2')
sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1)


Expected: (y2 + y3) * x1*y1

sage: var('x1 y1 y2 y3 z1')
sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1  )), (x1+cos(y1))*z1)
Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1  ))

sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1)
Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)]


Edit 10/04/2022:

Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is:

from sage.symbolic.operators import add_vararg, mul_vararg

def is_exact_positive_integer_multiple_of(x, test_multiple):
# returns None if x or test_multiple are not exact (containing floating point numbers).
# Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there
# is a positive integer n so that test_multiple == n * x. If there is no such integer or there
# is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists,
# is_exact_positive_integer_multiple_of(x, test_multiple) returns None.
# E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)))
# could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but
# it could also return None since sagemath may not be able to prove that this holds.
var('dummy')
if not (dummy + x).is_exact() or not (dummy + test_multiple).is_exact():
return None
x = (x + dummy).simplify_full() - dummy
test_multiple = (test_multiple + dummy).simplify_full() - dummy
if 0 == x:
return None
else:
if ((test_multiple / x).is_integer() or \
(x in CC and test_multiple in CC and QQbar(test_multiple / x).is_integer())) \
and test_multiple >= x:
return test_multiple / x
else:
return None

def is_ratio_gerater_equal_one(x, test):
# returns None if x or test are not exact (containing floating point numbers).
# Otherwise, returns test / x if sagemath can prove (by simplification, if necessary)
# that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1,
# is_ratio_gerater_equal_one(x, test) returns None
var('dummy')
if not (dummy + x).is_exact() or not (dummy + test).is_exact():
return None
x = (x + dummy).simplify_full() - dummy
test = (test + dummy).simplify_full() - dummy
if 0 == x:
return None
else:
if x.is_integer() and test.is_integer():
if (x > 0 and test > x-1) or (x < 0 and test < x+1):
# You may think that this if-interrogation is redundant due to the following
# interrogation for real x and test.
# But it seems that sagemath cannot infer that z >= 1 if you declare the following:
# var("y z")
# assume(y, "integer")
# assume(z, "integer", z>0)
# Without the preceding interrogation
#     is_ratio_gerater_equal_one(y, y+z-1)
# would yield False.
return test / x
else:
return None
elif test / x >= 1 or (x in CC and test in CC and QQbar(test / x) >= 1):
return test / x
else:
return None

def is_proven_integer(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool(x.is_integer() or (x in CC and QQbar(x).is_integer()))

def is_proven_nonnegative(x):
var('dummy x')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
if is_proven_real(x):
rr = True
elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \
(is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])):
rr = True
else:
rr = False
return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \
(x in CC and QQbar(x).is_real() and bool(QQbar(x) >= 0))

def is_proven_positive(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool((x.is_real() and x > 0) or (x in CC and QQbar(x) > 0))

def is_proven_real(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool(x.is_real() or (x in CC and QQbar(x).is_real())) or \
(x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \
(is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])))

def can_contract_consecutive_exponentiation(x, y, z):
# returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False.
return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z))

def try_concatenate_consecutive_exponentiations(expr):
if expr.operator() is not operator.pow:
return expr, 1
else:
base = expr.operands()[0]
expn = expr.operands()[1]
while base.operator() is operator.pow and \
can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn):
expn = base.operands()[1] * expn
base = base.operands()[0]
return base, expn

def can_expand_power_of_product(x, y, z):
# returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False.
return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z))

def remove_power_of_factor_from_product_or_power(expr, factor, exponent, max_exponent = False,
only_from_integer_powers = False):
if max_exponent:
return remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers)
else:
r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent,
only_from_integer_powers)
return r, None if r is None else 1

def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent, only_from_integer_powers):
# factor must contain at least one variable, and this variable must also be in expr.
# If expr == factor ** exponent, this function returns the pair 1, 1 .
# Otherwise: 'expr' must be a product or an exponentiation (power function).
# If 'factor' is a product or an exponentiation, look at all factors inside 'factor'
# that contain a variable (no constant factors), otherwise look at 'factor' as a whole. Under some conditions,
# remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values
#    expr / (factor ** exponent)   ,    nn
# otherwise, it returns None, None. These conditions are:
# --- if only_from_integer_powers==True:
#     all factor ???????????????????????????????????????????????????????????????????

if 0 == exponent or factor in CC or expr in CC:   # factor in CC: if factor is constant, i.e. if it contains no variables
return None, None
if factor.is_zero()  # this test should normally not be necessary due to above test factor in CC
return None, None

facop = factor.operator()
if facop is operator.pow:
factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent)
facop = factor.operator()

if facop == mul_vararg:
p = expr
intpow = None
for fac in factor.operands():
if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent):
return None, None
if fac in CC:  # if fac is constant, i.e. if it contains no variables
p /= fac ** exponent
else:
p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent,
only_from_integer_powers)
if p is None:
return None, None
if intpow is None:
intpow = nn
elif only_from_integer_powers and nn != intpow:
return None, None
return p,  nn if only_from_integer_powers else 0

op = expr.operator()
if op is None:
if expr == factor ** exponent:
return 1, 1 if only_from_integer_powers else 0
else:
return None, None
elif op is operator.pow and expr.operands()[0]==factor and \
(is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \
else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None:
# The second return value expr.operands()[1] / exponent is real (not complex, nonreal)
# because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one()
# condition.
return factor ** (expr.operands()[1] - exponent), \
expr.operands()[1] / exponent if only_from_integer_powers else 0
elif op == mul_vararg:
without = None
for i,fac in enumerate(expr.operands()):
if fac == factor ** exponent:
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without,  1 if only_from_integer_powers else 0
elif fac.operator() is operator.pow:
# x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiation.
base, expn = try_concatenate_consecutive_exponentiations(fac)
if base == factor and \
(is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \
else is_ratio_gerater_equal_one(exponent, expn)) is not None:
without = factor ** (expn - exponent)
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
# The second return value fac.operands()[1] / exponent is real (not complex, nonreal)
# because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one()
# condition.
return without,  fac.operands()[1] / exponent if only_from_integer_powers else 0
return None, None
else:
return None, None

def remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers):

if 0 == exponent or factor in CC or expr in CC:   # factor in CC: if factor is constant, i.e. if it contains no variables
return None, None

facop = factor.operator()
if facop is operator.pow:
factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent)
facop = factor.operator()

if facop == mul_vararg:
min_abs_exp = None
p = expr
for fac in factor.operands():
if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent):
return None, None
if fac in CC:  # if fac is constant, i.e. if it contains no variables
pass  # needn't divide by constant since p will be discarded and re-calculated below
else:
p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent,
only_from_integer_powers)
if p is None:
return None, None
if min_abs_exp is None or abs(nn) < min_abs_exp:
min_abs_exp = abs(nn)
p = expr
for fac in factor.operands():
if fac in CC:  # if fac is constant, i.e. if it contains no variables
p /= fac ** (min_abs_exp * exponent)
else:
p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac,
min_abs_exp * exponent,
only_from_integer_powers)
if p is None:
return None, None
return p, min_abs_exp
op = expr.operator()
if op is None:
if expr == factor ** exponent:
return 1, 1
else:
return None, None
elif op is operator.pow and expr.operands()[0]==factor and only_from_integer_powers and \
is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None:
# The second return ee is real (not complex, nonreal) because of the tested
# is_exact_positive_integer_multiple_of() condition.
ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1])
return 1, ee
elif op is operator.pow and expr.operands()[0]==factor and not only_from_integer_powers and \
is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None:
ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1])
# The second return ee is real (not complex, nonreal) because of the tested
# is_ratio_gerater_equal_one() condition.
return factor ** (expr.operands()[1] - exponent * ee), ee
elif op == mul_vararg:
without = None
for i,fac in enumerate(expr.operands()):
if fac == factor ** exponent:
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without, 1
elif fac.operator() is operator.pow:
# x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiation.
base, expn = try_concatenate_consecutive_exponentiations(fac)
if base == factor:
if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None:
ee = is_exact_positive_integer_multiple_of(exponent, expn)
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without, ee
elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None:
ee = is_ratio_gerater_equal_one(exponent, expn)
without = factor ** (expn - exponent * ee)
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
# The second return ee is real (not complex, nonreal)
# because of the tested is_ratio_gerater_equal_one() condition.
return without, ee
return None, None
else:
return None, None

def factor_out(expr, factor, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False,
replace_factor_by = None):

if type(expr) == list:
return [factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)
for ex in expr]
if type(expr) == tuple:
return tuple(factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)
for ex in expr)

if replace_factor_by is None:
replace_factor_by = factor

op = expr.operator()
if op is None or op != add_vararg:
if op is None or not recursive:
return expr
else:
return op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in expr.operands()])

summands = expr.operands()
factors_removed = []

if all_integer_powers:
not_containing = summands
while True:
fac_removed = []
summands = not_containing
max_exponent = None
for p in summands:
op = p.operator()
if op == mul_vararg or (op is operator.pow and p.operands()[0]==factor and p.operands()[1] >= 1):
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, True,
only_from_integer_powers)
if without_factor is not None:
if max_exponent is None or ee > max_exponent:
max_exponent = ee
if max_exponent is None:
break
not_containing = []  # must be after the preceeding if .. break statements
for p in summands:
op = p.operator()
if op == mul_vararg:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent,
False, only_from_integer_powers)
if without_factor is None:
if not recursive:
not_containing.append(p)
else:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
fac_removed.append(without_factor)
else:
fac_removed.append(
op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
elif op is operator.pow:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent,
False, only_from_integer_powers)
if without_factor is None:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
fac_removed.append(without_factor)
else:
fac_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
else:
if p == factor:
fac_removed.append(1)
else:
not_containing.append(factor_out(p, factor, recursive = recursive, all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by))
if 0==len(fac_removed):
break
else:
factors_removed.append( (fac_removed, max_exponent) )
return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing)

else:

not_containing = []
for p in summands:
op = p.operator()
if op == mul_vararg:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, False,
only_from_integer_powers)
if without_factor is None:
if not recursive:
not_containing.append(p)
else:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
factors_removed.append(without_factor)
else:
factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
elif op is operator.pow:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, all_integer_powers,
only_from_integer_powers)
if without_factor is None:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
factors_removed.append(without_factor)
else:
factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
else:
if p == factor:
factors_removed.append(1)
else:
not_containing.append(factor_out(p, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by))

return sum(factors_removed) * replace_factor_by + sum(not_containing)


I give some examples how to use it:

var('a b c d')
expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2)
factr = x
print("factor_out(", expr, ", ", factr, ")")
for allint in [False, True]:
for onlyfromint in [False, True]:
print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint))


The output is:

factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) ,  x )
allintpow =  False  onlyfromint =  False  result =  (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2)
allintpow =  False  onlyfromint =  True  result =  7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2)
allintpow =  True  onlyfromint =  False  result =  (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2)
allintpow =  True  onlyfromint =  True  result =  7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2)


If you find any bugs or have other suggestions, let me know.

I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely:

Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression

my_collect2((a+b)*x + x, x)


shall yield

(a+b+1) * x


Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact).

Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products.

More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's.

Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have

var('a b c x y z')
my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y)


I do not want to get

(a+b+2*c) * (x+y)


since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y).

Further more, my_collect2 should also be callable with three arguments:

my_collect2(expr, fact, subs_fact)


should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g.

sage: var('x1 y1 y2 y3 z1 z2 x1y1')
sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1)


Expected: (y2 + y3) * x1y1

It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products.

I give some examples:

sage: var('x1 y1 y2 y3 z1 z2')
sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1)


Expected: (y2 + y3) * x1*y1

sage: var('x1 y1 y2 y3 z1')
sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1  )), (x1+cos(y1))*z1)
Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1  ))

sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1)
Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)]


Edit 10/04/2022:

Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is:

from sage.symbolic.operators import add_vararg, mul_vararg

def is_exact_positive_integer_multiple_of(x, test_multiple):
# returns None if x or test_multiple are not exact (containing floating point numbers).
# Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there
# is a positive integer n so that test_multiple == n * x. If there is no such integer or there
# is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists,
# is_exact_positive_integer_multiple_of(x, test_multiple) returns None.
# E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)))
# could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but
# it could also return None since sagemath may not be able to prove that this holds.
var('dummy')
if not (dummy + x).is_exact() or not (dummy + test_multiple).is_exact():
return None
x = (x + dummy).simplify_full() - dummy
test_multiple = (test_multiple + dummy).simplify_full() - dummy
if 0 == x:
return None
else:
if ((test_multiple / x).is_integer() or \
(x in CC and test_multiple in CC and QQbar(test_multiple / x).is_integer())) \
and test_multiple >= x:
return test_multiple / x
else:
return None

def is_ratio_gerater_equal_one(x, test):
# returns None if x or test are not exact (containing floating point numbers).
# Otherwise, returns test / x if sagemath can prove (by simplification, if necessary)
# that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1,
# is_ratio_gerater_equal_one(x, test) returns None
var('dummy')
if not (dummy + x).is_exact() or not (dummy + test).is_exact():
return None
x = (x + dummy).simplify_full() - dummy
test = (test + dummy).simplify_full() - dummy
if 0 == x:
return None
else:
if x.is_integer() and test.is_integer():
if (x > 0 and test > x-1) or (x < 0 and test < x+1):
# You may think that this if-interrogation is redundant due to the following
# interrogation for real x and test.
# But it seems that sagemath cannot infer that z >= 1 if you declare the following:
# var("y z")
# assume(y, "integer")
# assume(z, "integer", z>0)
# Without the preceding interrogation
#     is_ratio_gerater_equal_one(y, y+z-1)
# would yield False.
return test / x
else:
return None
elif test / x >= 1 or (x in CC and test in CC and QQbar(test / x) >= 1):
return test / x
else:
return None

def is_proven_integer(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool(x.is_integer() or (x in CC and QQbar(x).is_integer()))

def is_proven_nonnegative(x):
var('dummy x')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
if is_proven_real(x):
rr = True
elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \
(is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])):
rr = True
else:
rr = False
return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \
(x in CC and QQbar(x).is_real() and bool(QQbar(x) >= 0))

def is_proven_positive(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool((x.is_real() and x > 0) or (x in CC and QQbar(x) > 0))

def is_proven_real(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool(x.is_real() or (x in CC and QQbar(x).is_real())) or \
(x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \
(is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])))

def can_contract_consecutive_exponentiation(x, y, z):
# returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False.
return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z))

def try_concatenate_consecutive_exponentiations(expr):
if expr.operator() is not operator.pow:
return expr, 1
else:
base = expr.operands()[0]
expn = expr.operands()[1]
while base.operator() is operator.pow and \
can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn):
expn = base.operands()[1] * expn
base = base.operands()[0]
return base, expn

def can_expand_power_of_product(x, y, z):
# returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False.
return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z))

def remove_power_of_factor_from_product_or_power(expr, factor, exponent, max_exponent = False,
only_from_integer_powers = False):
if max_exponent:
return remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers)
else:
r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent,
only_from_integer_powers)
return r, None if r is None else 1

def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent, only_from_integer_powers):
# factor must contain at least one variable, and this variable must also be in expr.
# If expr == factor ** exponent, this function returns the pair 1, 1 .
# Otherwise: 'expr' must be a product or an exponentiation (power function).
# If 'factor' is a product or an exponentiation, look at all factors inside 'factor'
# that contain a variable (no constant factors), otherwise look at 'factor' as a whole. Under some conditions,
# remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values
#    expr / (factor ** exponent)   ,    nn
# otherwise, it returns None, None. These conditions are:
# --- if only_from_integer_powers==True:
#     all factor ???????????????????????????????????????????????????????????????????

if 0 == exponent or factor in CC or expr in CC:   # factor in CC: if factor is constant, i.e. if it contains no variables
return None, None
if factor.is_zero()  # this test should normally not be necessary due to above test factor in CC
return None, None

facop = factor.operator()
if facop is operator.pow:
factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent)
facop = factor.operator()

if facop == mul_vararg:
p = expr
intpow = None
for fac in factor.operands():
if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent):
return None, None
if fac in CC:  # if fac is constant, i.e. if it contains no variables
p /= fac ** exponent
else:
p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent,
only_from_integer_powers)
if p is None:
return None, None
if intpow is None:
intpow = nn
elif only_from_integer_powers and nn != intpow:
return None, None
return p,  nn if only_from_integer_powers else 0

op = expr.operator()
if op is None:
if expr == factor ** exponent:
return 1, 1 if only_from_integer_powers else 0
else:
return None, None
elif op is operator.pow and expr.operands()[0]==factor and \
(is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \
else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None:
# The second return value expr.operands()[1] / exponent is real (not complex, nonreal)
# because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one()
# condition.
return factor ** (expr.operands()[1] - exponent), \
expr.operands()[1] / exponent if only_from_integer_powers else 0
elif op == mul_vararg:
without = None
for i,fac in enumerate(expr.operands()):
if fac == factor ** exponent:
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without,  1 if only_from_integer_powers else 0
elif fac.operator() is operator.pow:
# x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiation.
base, expn = try_concatenate_consecutive_exponentiations(fac)
if base == factor and \
(is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \
else is_ratio_gerater_equal_one(exponent, expn)) is not None:
without = factor ** (expn - exponent)
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
# The second return value fac.operands()[1] / exponent is real (not complex, nonreal)
# because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one()
# condition.
return without,  fac.operands()[1] / exponent if only_from_integer_powers else 0
return None, None
else:
return None, None

def remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers):

if 0 == exponent or factor in CC or expr in CC:   # factor in CC: if factor is constant, i.e. if it contains no variables
return None, None

facop = factor.operator()
if facop is operator.pow:
factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent)
facop = factor.operator()

if facop == mul_vararg:
min_abs_exp = None
p = expr
for fac in factor.operands():
if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent):
return None, None
if fac in CC:  # if fac is constant, i.e. if it contains no variables
pass  # needn't divide by constant since p will be discarded and re-calculated below
else:
p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent,
only_from_integer_powers)
if p is None:
return None, None
if min_abs_exp is None or abs(nn) < min_abs_exp:
min_abs_exp = abs(nn)
p = expr
for fac in factor.operands():
if fac in CC:  # if fac is constant, i.e. if it contains no variables
p /= fac ** (min_abs_exp * exponent)
else:
p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac,
min_abs_exp * exponent,
only_from_integer_powers)
if p is None:
return None, None
return p, min_abs_exp
op = expr.operator()
if op is None:
if expr == factor ** exponent:
return 1, 1
else:
return None, None
elif op is operator.pow and expr.operands()[0]==factor and only_from_integer_powers and \
is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None:
# The second return ee is real (not complex, nonreal) because of the tested
# is_exact_positive_integer_multiple_of() condition.
ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1])
return 1, ee
elif op is operator.pow and expr.operands()[0]==factor and not only_from_integer_powers and \
is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None:
ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1])
# The second return ee is real (not complex, nonreal) because of the tested
# is_ratio_gerater_equal_one() condition.
return factor ** (expr.operands()[1] - exponent * ee), ee
elif op == mul_vararg:
without = None
for i,fac in enumerate(expr.operands()):
if fac == factor ** exponent:
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without, 1
elif fac.operator() is operator.pow:
# x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiation.
base, expn = try_concatenate_consecutive_exponentiations(fac)
if base == factor:
if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None:
ee = is_exact_positive_integer_multiple_of(exponent, expn)
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without, ee
elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None:
ee = is_ratio_gerater_equal_one(exponent, expn)
without = factor ** (expn - exponent * ee)
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
# The second return ee is real (not complex, nonreal)
# because of the tested is_ratio_gerater_equal_one() condition.
return without, ee
return None, None
else:
return None, None

def factor_out(expr, factor, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False,
replace_factor_by = None):

if type(expr) == list:
return [factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)
for ex in expr]
if type(expr) == tuple:
return tuple(factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)
for ex in expr)

if replace_factor_by is None:
replace_factor_by = factor

op = expr.operator()
if op is None or op != add_vararg:
if op is None or not recursive:
return expr
else:
return op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in expr.operands()])

summands = expr.operands()
factors_removed = []

if all_integer_powers:
not_containing = summands
while True:
fac_removed = []
summands = not_containing
max_exponent = None
for p in summands:
op = p.operator()
if op == mul_vararg or (op is operator.pow and p.operands()[0]==factor and p.operands()[1] >= 1):
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, True,
only_from_integer_powers)
if without_factor is not None:
if max_exponent is None or ee > max_exponent:
max_exponent = ee
if max_exponent is None:
break
not_containing = []  # must be after the preceeding if .. break statements
for p in summands:
op = p.operator()
if op == mul_vararg:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent,
False, only_from_integer_powers)
if without_factor is None:
if not recursive:
not_containing.append(p)
else:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
fac_removed.append(without_factor)
else:
fac_removed.append(
op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
elif op is operator.pow:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent,
False, only_from_integer_powers)
if without_factor is None:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
fac_removed.append(without_factor)
else:
fac_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
else:
if p == factor:
fac_removed.append(1)
else:
not_containing.append(factor_out(p, factor, recursive = recursive, all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by))
if 0==len(fac_removed):
break
else:
factors_removed.append( (fac_removed, max_exponent) )
return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing)

else:

not_containing = []
for p in summands:
op = p.operator()
if op == mul_vararg:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, False,
only_from_integer_powers)
if without_factor is None:
if not recursive:
not_containing.append(p)
else:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
factors_removed.append(without_factor)
else:
factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
elif op is operator.pow:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, all_integer_powers,
only_from_integer_powers)
if without_factor is None:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
factors_removed.append(without_factor)
else:
factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
else:
if p == factor:
factors_removed.append(1)
else:
not_containing.append(factor_out(p, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by))

return sum(factors_removed) * replace_factor_by + sum(not_containing)


I give some examples how to use it:

var('a b c d')
expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2)
factr = x
print("factor_out(", expr, ", ", factr, ")")
for allint in [False, True]:
for onlyfromint in [False, True]:
print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint))


The output is:

factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) ,  x )
allintpow =  False  onlyfromint =  False  result =  (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2)
allintpow =  False  onlyfromint =  True  result =  7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2)
allintpow =  True  onlyfromint =  False  result =  (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2)
allintpow =  True  onlyfromint =  True  result =  7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2)


If you find any bugs or have other suggestions, let me know.

I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely:

Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression

my_collect2((a+b)*x + x, x)


shall yield

(a+b+1) * x


Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact).

Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products.

More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's.

Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have

var('a b c x y z')
my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y)


I do not want to get

(a+b+2*c) * (x+y)


since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y).

Further more, my_collect2 should also be callable with three arguments:

my_collect2(expr, fact, subs_fact)


should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g.

sage: var('x1 y1 y2 y3 z1 z2 x1y1')
sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1)


Expected: (y2 + y3) * x1y1

It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products.

I give some examples:

sage: var('x1 y1 y2 y3 z1 z2')
sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1)


Expected: (y2 + y3) * x1*y1

sage: var('x1 y1 y2 y3 z1')
sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1  )), (x1+cos(y1))*z1)
Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1  ))

sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1)
Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)]


Edit 10/04/2022:

Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is:

from sage.symbolic.operators import add_vararg, mul_vararg

mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple):
# returns None if x or test_multiple are not exact (containing floating point numbers).
# Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there
# is a positive integer n so that test_multiple == n * x. If there is no such integer or there
# is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists,
# is_exact_positive_integer_multiple_of(x, test_multiple) returns None.
# E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) 12(cos(3/7pi) - cos(2/7*pi) cos(2/7pi) + cos(1/7*pi)))
cos(1/7pi)))
# could return 2 since 12*(cos(3/7*pi) 12(cos(3/7pi) - cos(2/7*pi) cos(2/7pi) + cos(1/7*pi)) cos(1/7pi)) == 2*3, but
# it could also return None since sagemath may not be able to prove that this holds.
var('dummy')
if not (dummy + x).is_exact() SR(x).is_exact() or not (dummy + test_multiple).is_exact():
SR(test_multiple).is_exact():
return None
x = (x + dummy).simplify_full() - dummy
SR(x).simplify_full()
test_multiple = (test_multiple + dummy).simplify_full() - dummy
SR(test_multiple).simplify_full()
if 0 == x:
return None
else:
if ((test_multiple / x).is_integer() or \
(x in CC (SR(x).is_constant() and test_multiple in CC SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \
and test_multiple >= x:
return test_multiple / x
else:
return None

None def is_ratio_gerater_equal_one(x, test):
# returns None if x or test are not exact (containing floating point numbers).
# Otherwise, returns test / x if sagemath can prove (by simplification, if necessary)
# that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1,
# is_ratio_gerater_equal_one(x, test) returns None
var('dummy')
None.
if not (dummy + x).is_exact() SR(x).is_exact() or not (dummy + test).is_exact():
SR(test).is_exact():
return None
x = (x + dummy).simplify_full() - dummy
SR(x).simplify_full()
test = (test + dummy).simplify_full() - dummy
SR(test).simplify_full()
if 0 == x:
return None
else:
if x.is_integer() and test.is_integer():
if (x > 0 and test > x-1) or (x < 0 and test < x+1):
# You may think that this if-interrogation is redundant due to the following
# interrogation for real x and test.
# But it seems that sagemath cannot infer that z >= 1 if you declare the following:
# var("y z")
# assume(y, "integer")
# assume(z, "integer", z>0)
# Without the preceding interrogation
#     is_ratio_gerater_equal_one(y, y+z-1)
# would yield False.
return test / x
else:
return None
elif test / x >= 1 or (x in CC (SR(x).is_constant() and test in CC SR(test).is_constant() and QQbar(test / x) >= 1):
return test / x
else:
return None

None def is_proven_integer(x):
var('dummy')
if not (dummy + x).is_exact():
SR(x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
SR(x).simplify_full()
return bool(x.is_integer() or (x in CC ((x).is_constant() and QQbar(x).is_integer()))

QQbar(x).is_integer())) def is_proven_nonnegative(x):
var('dummy x')
if not (dummy + x).is_exact():
SR(x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
SR(x).simplify_full()
if is_proven_real(x):
rr = True
elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \
(is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])):
rr = True
else:
rr = False
return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \
(x in CC ((x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0))

0)) def is_proven_positive(x):
var('dummy')
is_proven_real(x):
if not (dummy + x).is_exact():
SR(x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
return bool((x.is_real() and x > 0) or (x in CC and QQbar(x) > 0))

def is_proven_real(x):
var('dummy')
if not (dummy + x).is_exact():
return False
x = (x + dummy).simplify_full() - dummy
SR(x).simplify_full()
return bool(x.is_real() or (x in CC ((x).is_constant() and QQbar(x).is_real())) or \
(x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \
(is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])))

is_proven_integer(x.operands()[1]))) def is_proven_positive(x):
if not SR(x).is_exact():
return False
x = SR(x).simplify_full()
return bool((is_proven_real(x) and x > 0) or ((x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z):
# returns True if a sufficient condition for (x ** * y) ** * z == x ** (y*z) * (yz) could be found, otherwise False.
return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z))

is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr):
if expr.operator() is not operator.pow:
return expr, 1
else:
base = expr.operands()[0]
expn = expr.operands()[1]
while base.operator() is operator.pow and \
can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn):
expn = base.operands()[1] * expn
base = base.operands()[0]
return base, expn

expn def can_expand_power_of_product(x, y, z):
# returns True if a sufficient condition for (x*y) ** (xy) * z == x**z xz * y**z yz could be found, otherwise False.
return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z))

is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factor, exponent, max_exponent = False,
only_from_integer_powers = False):
if max_exponent:
return remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers)
else:
r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent,
only_from_integer_powers)
return r, None if r is None else 1

1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent, only_from_integer_powers):
# factor must contain at least one variable, and this variable must also be in expr.
# If expr == factor ** * exponent, this function returns the pair 1, 1 .
# Otherwise: 'expr' must be a product or an exponentiation (power function).
# If 'factor' is a product or an exponentiation, look at all factors inside 'factor'
# that contain a variable (no constant factors), otherwise look at 'factor' as a whole. Under some conditions,
# remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values
#    expr / (factor ** * exponent)   ,    nn
# otherwise, it returns None, None. These conditions are:
# --- if only_from_integer_powers==True:
#     all factor ???????????????????????????????????????????????????????????????????

tbd if 0 == exponent or factor in CC SR(factor).is_constant() or expr in CC: SR(expr).is_constant():   # factor in CC: SR(factor).is_constant(): if factor is constant, i.e. if it contains no variables
return None, None
if factor.is_zero() if factor.is_zero():  # this test should normally not be necessary due to above test factor "factor in CC
CC"
return None, None

facop = factor.operator()
if facop is operator.pow:
factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent)
facop = factor.operator()

if facop == mul_vararg:
p = expr
intpow = None
for fac in factor.operands():
if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent):
return None, None
if fac in CC: if SR(fac).is_constant():  # if fac is constant, i.e. if it contains no variables
p /= fac ** exponent
else:
p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent,
only_from_integer_powers)
if p is None:
return None, None
if intpow is None:
intpow = nn
elif only_from_integer_powers and nn != intpow:
return None, None
return p,  nn if only_from_integer_powers else 0

op = expr.operator()
if op is None:
if expr == factor ** exponent:
return 1, 1 if only_from_integer_powers else 0
else:
return None, None
elif op is operator.pow and expr.operands()[0]==factor and \
(is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \
else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None:
# The second return value expr.operands()[1] / exponent is real (not complex, nonreal)
# because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one()
# condition.
return factor ** (expr.operands()[1] - exponent), \
expr.operands()[1] / exponent if only_from_integer_powers else 0
elif op == mul_vararg:
without = None
for i,fac in enumerate(expr.operands()):
if fac == factor ** exponent:
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without,  1 if only_from_integer_powers else 0
elif fac.operator() is operator.pow:
# If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiation.
exponentiations.
base, expn = try_concatenate_consecutive_exponentiations(fac)
if base == factor and \
(is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \
else is_ratio_gerater_equal_one(exponent, expn)) is not None:
without = factor ** (expn - exponent)
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
# The second return value fac.operands()[1] / exponent is real (not complex, nonreal)
# because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one()
# condition.
return without,  fac.operands()[1] / exponent if only_from_integer_powers else 0
return None, None
else:
return None, None
def remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers):

only_from_integer_powers): if 0 == exponent or factor in CC SR(factor).is_constant() or expr in CC: SR(expr).is_constant():   # factor in CC: SR(factor).is_constant(): if factor is constant, i.e. if it contains no variables
return None, None
if factor.is_zero():  # this test should normally not be necessary due to above test "factor in CC"
return None, None

facop = factor.operator()
if facop is operator.pow:
factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent)
facop = factor.operator()

if facop == mul_vararg:
min_abs_exp = None
p = expr
for fac in factor.operands():
if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent):
return None, None
if fac in CC: if SR(fac).is_constant():  # if fac is constant, i.e. if it contains no variables
pass  # needn't divide by constant since p will be discarded and re-calculated below
else:
p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent,
only_from_integer_powers)
if p is None:
return None, None
if min_abs_exp is None or abs(nn) < min_abs_exp:
min_abs_exp = abs(nn)
p = expr
for fac in factor.operands():
if fac in CC: if SR(fac).is_constant():  # if fac is constant, i.e. if it contains no variables
p /= fac ** (min_abs_exp * exponent)
else:
p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac,
min_abs_exp * exponent,
only_from_integer_powers)
if p is None:
return None, None
return p, min_abs_exp
op = expr.operator()
if op is None:
if expr == factor ** exponent:
return 1, 1
else:
return None, None
elif op is operator.pow and expr.operands()[0]==factor and only_from_integer_powers and \
is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None:
# The second return ee is real (not complex, nonreal) because of the tested
# is_exact_positive_integer_multiple_of() condition.
ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1])
return 1, ee
elif op is operator.pow and expr.operands()[0]==factor and not only_from_integer_powers and \
is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None:
ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1])
# The second return ee is real (not complex, nonreal) because of the tested
# is_ratio_gerater_equal_one() condition.
return factor ** (expr.operands()[1] - exponent * ee), ee
elif op == mul_vararg:
without = None
for i,fac in enumerate(expr.operands()):
if fac == factor ** exponent:
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without, 1
elif fac.operator() is operator.pow:
# If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiation.
exponentiations.
base, expn = try_concatenate_consecutive_exponentiations(fac)
if base == factor:
if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None:
ee = is_exact_positive_integer_multiple_of(exponent, expn)
without = 1
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
return without, ee
elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None:
ee = is_ratio_gerater_equal_one(exponent, expn)
without = factor ** (expn - exponent * ee)
for j,fac2 in enumerate(expr.operands()):
if j != i:
without *= fac2
# The second return ee is real (not complex, nonreal)
# because of the tested is_ratio_gerater_equal_one() condition.
return without, ee
return None, None
else:
return None, None
def factor_out(expr, factor, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False,
replace_factor_by = None):

None): if type(expr) == list:
return [factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)
for ex in expr]
if type(expr) == tuple:
return tuple(factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)
for ex in expr)

if replace_factor_by is None:
replace_factor_by = factor

op = expr.operator()
if op is None or op != add_vararg:
if op is None or not recursive:
return expr
else:
return op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in expr.operands()])

summands = expr.operands()
factors_removed = []

if all_integer_powers:
not_containing = summands
while True:
fac_removed = []
summands = not_containing
max_exponent = None
for p in summands:
op = p.operator()
if op == mul_vararg or (op is operator.pow and p.operands()[0]==factor and p.operands()[1] >= 1):
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, True,
only_from_integer_powers)
if without_factor is not None:
if max_exponent is None or ee > max_exponent:
max_exponent = ee
if max_exponent is None:
break
not_containing = []  # must be after the preceeding if .. break statements
for p in summands:
op = p.operator()
if op == mul_vararg:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent,
False, only_from_integer_powers)
if without_factor is None:
if not recursive:
not_containing.append(p)
else:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
fac_removed.append(without_factor)
else:
fac_removed.append(
op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
elif op is operator.pow:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent,
False, only_from_integer_powers)
if without_factor is None:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
fac_removed.append(without_factor)
else:
fac_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
else:
if p == factor:
fac_removed.append(1)
else:
not_containing.append(factor_out(p, factor, recursive = recursive, all_integer_powers = True,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by))
if 0==len(fac_removed):
break
else:
factors_removed.append( (fac_removed, max_exponent) )
return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing)

else:

not_containing = []
for p in summands:
op = p.operator()
if op == mul_vararg:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, False,
only_from_integer_powers)
if without_factor is None:
if not recursive:
not_containing.append(p)
else:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
factors_removed.append(without_factor)
else:
factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
elif op is operator.pow:
without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, all_integer_powers,
only_from_integer_powers)
if without_factor is None:
not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in p.operands()]))
else:
op = without_factor.operator()
if op is None or not recursive:
factors_removed.append(without_factor)
else:
factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by)
for ex in without_factor.operands()]))
else:
if p == factor:
factors_removed.append(1)
else:
not_containing.append(factor_out(p, factor, recursive = recursive,
all_integer_powers = all_integer_powers,
only_from_integer_powers = only_from_integer_powers,
replace_factor_by = replace_factor_by))

return sum(factors_removed) * replace_factor_by + sum(not_containing)
I give some examples how to use it: var('a b c d')
expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2)
factr = x
print("factor_out(", expr, ", ", factr, ")")
for allint in [False, True]:
for onlyfromint in [False, True]:
print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint))
The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) ,  x )
allintpow =  False  onlyfromint =  False  result =  (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2)
allintpow =  False  onlyfromint =  True  result =  7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2)
allintpow =  True  onlyfromint =  False  result =  (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2)
allintpow =  True  onlyfromint =  True  result =  7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2)
If you find any bugs or have other suggestions, let me know. 

 11 No.11 Revision updated 2022-10-06 06:11:50 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12(cos(3/7pi) 12*(cos(3/7*pi) - cos(2/7pi) cos(2/7*pi) + cos(1/7pi))) cos(1/7*pi))) # could return 2 since 12(cos(3/7pi) 12*(cos(3/7*pi) - cos(2/7pi) cos(2/7*pi) + cos(1/7pi)) cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return NoneNone def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if 0 == x: return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return NoneNone def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or ((x).is_constant() and QQbar(x).is_integer()))QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ ((x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0))0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or ((x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])))is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or ((x).is_constant() and QQbar(x) > 0))0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x * ** y) * ** z == x * (yz) ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z))is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expnexpn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (xy) * (x*y) ** z == xz x**z * yz y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z))is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factor, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent, only_from_integer_powers) return r, None if r is None else 11 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent, only_from_integer_powers): # factor must contain at least one variable, and this variable must also be in expr. # If expr == factor * ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factor' is a product or an exponentiation, look at all factors inside 'factor' # that contain a variable (no constant factors), otherwise look at 'factor' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factor * ** exponent) , nn # otherwise, it returns None, None. These conditions are: # tbdtbd if 0 == exponent or SR(factor).is_constant() or SR(expr).is_constant(): # SR(factor).is_constant(): if factor is constant, i.e. if it contains no variables return None, None if factor.is_zero(): # this test should normally not be necessary due to above test "factor in CC" return None, None facop = factor.operator() if facop is operator.pow: factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent) facop = factor.operator() if facop == mul_vararg: p = expr intpow = None for fac in factor.operands(): if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if expr == factor ** exponent: return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factor and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factor ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif op == mul_vararg: without = None for i,fac in enumerate(expr.operands()): if fac == factor ** exponent: without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if base == factor and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factor ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers):only_from_integer_powers): if 0 == exponent or SR(factor).is_constant() or SR(expr).is_constant(): # SR(factor).is_constant(): if factor is constant, i.e. if it contains no variables return None, None if factor.is_zero(): # this test should normally not be necessary due to above test "factor in CC" return None, None facop = factor.operator() if facop is operator.pow: factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent) facop = factor.operator() if facop == mul_vararg: min_abs_exp = None p = expr for fac in factor.operands(): if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factor.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if expr == factor ** exponent: return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factor and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factor and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factor ** (expr.operands()[1] - exponent * ee), ee elif op == mul_vararg: without = None for i,fac in enumerate(expr.operands()): if fac == factor ** exponent: without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if base == factor: if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factor ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factor, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None):None): if type(expr) == list: return [factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factor op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factor and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if p == factor: fac_removed.append(1) else: not_containing.append(factor_out(p, factor, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if p == factor: factors_removed.append(1) else: not_containing.append(factor_out(p, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2) allintpow = False onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) If you find any bugs or have other suggestions, let me know. 12 No.12 Revision updated 2022-10-06 06:12:40 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_varargmul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if 0 == x: return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or ((x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ ((x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or ((x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or ((x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factor, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, exponent, only_from_integer_powers): # factor must contain at least one variable, and this variable must also be in expr. # If expr == factor ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factor' is a product or an exponentiation, look at all factors inside 'factor' # that contain a variable (no constant factors), otherwise look at 'factor' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factor ** exponent) , nn # otherwise, it returns None, None. These conditions are: # tbd if 0 == exponent or SR(factor).is_constant() or SR(expr).is_constant(): # SR(factor).is_constant(): if factor is constant, i.e. if it contains no variables return None, None if factor.is_zero(): # this test should normally not be necessary due to above test "factor in CC" return None, None facop = factor.operator() if facop is operator.pow: factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent) facop = factor.operator() if facop == mul_vararg: p = expr intpow = None for fac in factor.operands(): if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if expr == factor ** exponent: return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factor and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factor ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif op == mul_vararg: without = None for i,fac in enumerate(expr.operands()): if fac == factor ** exponent: without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if base == factor and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factor ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, exponent, only_from_integer_powers): if 0 == exponent or SR(factor).is_constant() or SR(expr).is_constant(): # SR(factor).is_constant(): if factor is constant, i.e. if it contains no variables return None, None if factor.is_zero(): # this test should normally not be necessary due to above test "factor in CC" return None, None facop = factor.operator() if facop is operator.pow: factor, exponent = try_concatenate_consecutive_exponentiations(factor ** exponent) facop = factor.operator() if facop == mul_vararg: min_abs_exp = None p = expr for fac in factor.operands(): if not can_expand_power_of_product((factor / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factor.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if expr == factor ** exponent: return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factor and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factor and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factor ** (expr.operands()[1] - exponent * ee), ee elif op == mul_vararg: without = None for i,fac in enumerate(expr.operands()): if fac == factor ** exponent: without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if base == factor: if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factor ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factor, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factor op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factor and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if p == factor: fac_removed.append(1) else: not_containing.append(factor_out(p, factor, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if p == factor: factors_removed.append(1) else: not_containing.append(factor_out(p, factor, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2) allintpow = False onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) If you find any bugs or have other suggestions, let me know. 13 No.13 Revision updated 2022-10-06 06:19:32 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if 0 == x: return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or ((x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ ((x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or ((x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or ((x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factor, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factor, factr, exponent, only_from_integer_powers): # factor factr must contain at least one variable, and this variable must also be in expr. # If expr == factor factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factor' 'factr' is a product or an exponentiation, look at all factors inside 'factor' 'factr' # that contain a variable (no constant factors), otherwise look at 'factor' 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factor (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # tbd --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factor).is_constant() SR(factr).is_constant() or SR(expr).is_constant(): # SR(factor).is_constant(): SR(factr).is_constant(): if factor factr is constant, i.e. if it contains no variables return None, None if factor.is_zero(): factr.is_zero(): # this test should normally not be necessary due to above test "factor "factr in CC" return None, None facop = factor.operator() factr.operator() if facop is operator.pow: factor, factr, exponent = try_concatenate_consecutive_exponentiations(factor try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factor.operator() factr.operator() if facop == mul_vararg: p = expr intpow = None for fac in factor.operands(): factr.operands(): if not can_expand_power_of_product((factor can_expand_power_of_product((factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if expr == factor factr ** exponent: return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factor expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factor factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif op == mul_vararg: without = None for i,fac in enumerate(expr.operands()): if fac == factor factr ** exponent: without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if base == factor factr and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factor factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factor, factr, exponent, only_from_integer_powers): if 0 == exponent or SR(factor).is_constant() SR(factr).is_constant() or SR(expr).is_constant(): # SR(factor).is_constant(): SR(factr).is_constant(): if factor factr is constant, i.e. if it contains no variables return None, None if factor.is_zero(): factr.is_zero(): # this test should normally not be necessary due to above test "factor "factr in CC" return None, None facop = factor.operator() factr.operator() if facop is operator.pow: factor, factr, exponent = try_concatenate_consecutive_exponentiations(factor try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factor.operator() factr.operator() if facop == mul_vararg: min_abs_exp = None p = expr for fac in factor.operands(): factr.operands(): if not can_expand_power_of_product((factor can_expand_power_of_product((factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factor.operands(): factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if expr == factor factr ** exponent: return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factor expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factor expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factor factr ** (expr.operands()[1] - exponent * ee), ee elif op == mul_vararg: without = None for i,fac in enumerate(expr.operands()): if fac == factor factr ** exponent: without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if base == factor: factr: if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factor factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factor, factr, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factor factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factor p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if p == factor: factr: fac_removed.append(1) else: not_containing.append(factor_out(p, factor, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factor, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factor, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if p == factor: factr: factors_removed.append(1) else: not_containing.append(factor_out(p, factor, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2) allintpow = False onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) If you find any bugs or have other suggestions, let me know. 14 No.14 Revision updated 2022-10-06 06:58:51 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if 0 == x: x.is_zero(): return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or ((x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ ((x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or ((x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or ((x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers): # factr must contain at least one variable, and this variable must also be in expr. # If expr == factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factr' is a product or an exponentiation, look at all factors inside 'factr' # that contain a variable (no constant factors), otherwise look at 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None if factr.is_zero(): # this test should normally not be necessary due to above test "factr in CC" return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if facop bool(facop == mul_vararg: mul_vararg): p = expr intpow = None for fac in factr.operands(): if not can_expand_power_of_product((factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if expr bool(expr == factr ** exponent: exponent): return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif op bool(op == mul_vararg: mul_vararg): without = None for i,fac in enumerate(expr.operands()): if fac bool(fac == factr ** exponent: exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if base bool(base == factr factr) and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers): if 0 == exponent SR(exponent).is_zero() or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None if factr.is_zero(): # this test should normally not be necessary due to above test "factr in CC" return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if facop bool(facop == mul_vararg: mul_vararg): min_abs_exp = None p = expr for fac in factr.operands(): if not can_expand_power_of_product((factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if expr bool(expr == factr ** exponent: exponent): return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factr ** (expr.operands()[1] - exponent * ee), ee elif op bool(op == mul_vararg: mul_vararg): without = None for i,fac in enumerate(expr.operands()): if fac bool(fac == factr ** exponent: exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if base bool(base == factr: factr): if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factr, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if p bool(p == factr: factr): fac_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if p bool(p == factr: factr): factors_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2) allintpow = False onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) If you find any bugs or have other suggestions, let me know. 15 No.15 Revision updated 2022-10-07 06:30:36 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if x.is_zero(): return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or ((x).is_constant() (SR(x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ ((x).is_constant() (SR(x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or ((x).is_constant() (SR(x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or ((x).is_constant() (SR(x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers): # factr must contain at least one variable, and this variable must also be in expr. # If expr == factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factr' is a product or an exponentiation, look at all factors inside 'factr' # that contain a variable (no constant factors), otherwise look at 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): p = expr intpow = None for fac in factr.operands(): if not can_expand_power_of_product((factr can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr) and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers): if SR(exponent).is_zero() or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): min_abs_exp = None p = expr for fac in factr.operands(): if not can_expand_power_of_product((factr can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factr ** (expr.operands()[1] - exponent * ee), ee elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr): if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factr, *, recursive = True, all_integer_powers all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factr, recursive recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factr, recursive recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): fac_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): factors_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2) allintpow = False onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) If you find any bugs or have other suggestions, let me know. 16 No.16 Revision updated 2022-10-07 06:36:34 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if x.is_zero(): return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or (SR(x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ (SR(x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or (SR(x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or (SR(x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers): # factr must contain at least one variable, and this variable must also be in expr. # If expr == factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factr' is a product or an exponentiation, look at all factors inside 'factr' # that contain a variable (no constant factors), otherwise look at 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): p = expr intpow = None for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr) and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers): if SR(exponent).is_zero() or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): min_abs_exp = None p = expr for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factr ** (expr.operands()[1] - exponent * ee), ee elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr): if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factr, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): fac_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): factors_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2) allintpow = False onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) var('x1 y1 y2 y3 z1 z2') factor_out(x1y1y2 + y3x1y1, x1*y1) Output: x1y1(y2 + y3) If you find any bugs or have other suggestions, let me know. 17 No.17 Revision updated 2022-10-07 06:38:07 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if x.is_zero(): return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or (SR(x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ (SR(x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or (SR(x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or (SR(x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers): # factr must contain at least one variable, and this variable must also be in expr. # If expr == factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factr' is a product or an exponentiation, look at all factors inside 'factr' # that contain a variable (no constant factors), otherwise look at 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): p = expr intpow = None for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr) and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers): if SR(exponent).is_zero() or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): min_abs_exp = None p = expr for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factr ** (expr.operands()[1] - exponent * ee), ee elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr): if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factr, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): fac_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): factors_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2) allintpow = False onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) var('x1 y1 y2 y3 z1 z2') factor_out(x1y1y2 + y3x1y1, x1*y1) Output: x1y1(y2 + y3) If you find any bugs or have other suggestions, let me know. 18 No.18 Revision updated 2022-10-07 06:39:08 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if x.is_zero(): return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or (SR(x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ (SR(x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or (SR(x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or (SR(x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers): # factr must contain at least one variable, and this variable must also be in expr. # If expr == factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factr' is a product or an exponentiation, look at all factors inside 'factr' # that contain a variable (no constant factors), otherwise look at 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): p = expr intpow = None for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr) and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers): if SR(exponent).is_zero() or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): min_abs_exp = None p = expr for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factr ** (expr.operands()[1] - exponent * ee), ee elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr): if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factr, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): fac_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): factors_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2) allintpow = False onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) var('x1 y1 y2 y3 z1 z2') factor_out(x1y1y2 + y3x1y1, x1*y1) Output: x1y1(y2 + y3) If you find any bugs or have other suggestions, let me know. 19 No.19 Revision updated 2022-10-07 06:40:45 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if x.is_zero(): return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or (SR(x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ (SR(x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or (SR(x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or (SR(x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers): # factr must contain at least one variable, and this variable must also be in expr. # If expr == factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factr' is a product or an exponentiation, look at all factors inside 'factr' # that contain a variable (no constant factors), otherwise look at 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): p = expr intpow = None for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr) and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers): if SR(exponent).is_zero() or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): min_abs_exp = None p = expr for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factr ** (expr.operands()[1] - exponent * ee), ee elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr): if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factr, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): fac_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): factors_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2) allintpow = False onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) var('x1 y1 y2 y3 z1 z2') factor_out(x1y1y2 factor_out(x1*y1*y2 + y3x1y1, x1*y1)y3*x1*y1, x1*y1) Output: x1y1(y2 x1*y1*(y2 + y3)y3) If you find any bugs or have other suggestions, let me know. 20 No.20 Revision updated 2022-10-07 06:45:04 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if x.is_zero(): return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or (SR(x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ (SR(x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or (SR(x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or (SR(x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers): # factr must contain at least one variable, and this variable must also be in expr. # If expr == factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factr' is a product or an exponentiation, look at all factors inside 'factr' # that contain a variable (no constant factors), otherwise look at 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): p = expr intpow = None for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr) and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers): if SR(exponent).is_zero() or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): min_abs_exp = None p = expr for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factr ** (expr.operands()[1] - exponent * ee), ee elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr): if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factr, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): fac_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): factors_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') expr = x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) - 5*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - 5/x^(9/2) allintpow = False onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (7*a + 1)*x^(3/2) + 3*x^(5/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - 5/x^(9/2) var('x1 y1 y2 y3 z1 z2') factor_out(x1*y1*y2 + y3*x1*y1, x1*y1) Output: x1*y1*(y2 + y3) var('a b x') factor_out((a+b)*x + x, x) Output: (a + b + 1)*x If you find any bugs or have other suggestions, let me know. 21 No.21 Revision updated 2022-10-07 06:54:46 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if x.is_zero(): return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or (SR(x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ (SR(x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or (SR(x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or (SR(x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers): # factr must contain at least one variable, and this variable must also be in expr. # If expr == factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factr' is a product or an exponentiation, look at all factors inside 'factr' # that contain a variable (no constant factors), otherwise look at 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): p = expr intpow = None for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr) and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers): if SR(exponent).is_zero() or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): min_abs_exp = None p = expr for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factr ** (expr.operands()[1] - exponent * ee), ee elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr): if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factr, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): fac_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): factors_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c d') x') expr = x^(3/2) 7*a*x^(3/2) + 3*x^(5/2) + 7*a*x^(3/2) b*x^(5/2) + x^(3/2) - 5*x^(-9/2) 5*x^(-9/2) - c*x^(-9/2) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( b*x^(5/2) + 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - c/x^(9/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (7*a*sqrt(x) (b*x^(3/2) + 7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x - c/x^(9/2) - 5/x^(9/2) allintpow = False onlyfromint = True result = b*x^(5/2) + 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - c/x^(9/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (b + 3)*x^(5/2) + (7*a + 1)*x^(3/2) + 3*x^(5/2) - c/x^(9/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = b*x^(5/2) + 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - c/x^(9/2) - 5/x^(9/2) var('x1 y1 y2 y3 z1 z2') factor_out(x1*y1*y2 + y3*x1*y1, x1*y1) Output: x1*y1*(y2 + y3) var('a b x') factor_out((a+b)*x + x, x) Output: (a + b + 1)*x If you find any bugs or have other suggestions, let me know. 22 No.22 Revision updated 2022-10-07 07:01:21 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if x.is_zero(): return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or (SR(x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ (SR(x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or (SR(x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or (SR(x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers): # factr must contain at least one variable, and this variable must also be in expr. # If expr == factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factr' is a product or an exponentiation, look at all factors inside 'factr' # that contain a variable (no constant factors), otherwise look at 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): p = expr intpow = None for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr) and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers): if SR(exponent).is_zero() or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): min_abs_exp = None p = expr for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factr ** (expr.operands()[1] - exponent * ee), ee elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr): if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factr, *, recursive = True, all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): fac_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): factors_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c x') expr = 7*a*x^(3/2) + 3*x^(5/2) + b*x^(5/2) + x^(3/2) - 5*x^(-9/2) - c*x^(-9/2) c*x^(-9/2) + a*x^5 + 2*x^5 + 7*x^3 + c*x^3 + c*x^(-3) + 9*x^(-3) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( a*x^5 + 2*x^5 + c*x^3 + b*x^(5/2) + 7*x^3 + 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) + c/x^3 + 9/x^3 - c/x^(9/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (a*x^4 + 2*x^4 + c*x^2 + b*x^(3/2) + 7*x^2 + 7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x + c/x^3 + 9/x^3 - c/x^(9/2) - 5/x^(9/2) allintpow = False onlyfromint = True result = b*x^(5/2) + 7*a*x^(3/2) + 3*x^(5/2) + (a*x^4 + 2*x^4 + c*x^2 + 7*x^2)*x + x^(3/2) + c/x^3 + 9/x^3 - c/x^(9/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (a + 2)*x^5 + (c + 7)*x^3 + (b + 3)*x^(5/2) + (7*a + 1)*x^(3/2) + c/x^3 + 9/x^3 - c/x^(9/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = (a + 2)*x^5 + (c + 7)*x^3 + b*x^(5/2) + 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - c/x^(9/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (b*x^(3/2) + 7*a*sqrt(x) c/x^3 + 3*x^(3/2) + sqrt(x))*x - c/x^(9/2) - 5/x^(9/2) allintpow = False onlyfromint = True result = b*x^(5/2) + 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) - c/x^(9/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (b + 3)*x^(5/2) + (7*a + 1)*x^(3/2) - c/x^(9/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = b*x^(5/2) + 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) 9/x^3 - c/x^(9/2) - 5/x^(9/2) var('x1 y1 y2 y3 z1 z2') factor_out(x1*y1*y2 + y3*x1*y1, x1*y1) Output: x1*y1*(y2 + y3) var('a b x') factor_out((a+b)*x + x, x) Output: (a + b + 1)*x If you find any bugs or have other suggestions, let me know. 23 No.23 Revision updated 2022-10-08 07:18:14 +0200 I reformulate my wish for a function - call it my_collect2(expr, fact) - more precisely: Assume expr is a sum of products. Each factor in each product can be a constant, a single variable or any other expression like a+b, xy, sin(axy), ... If the sum contains also a single variable (rather than a product), I consider this variable also as "product", e.g. in (a+b)x + x the last x shall also be considered as "product" and so this whole expression is a sum of products. For this expression my_collect2((a+b)*x + x, x) shall yield (a+b+1) * x Generally assume that fact is also any expression. If fact occures as common factor in two or more of the products, fact should be factored out by the call my_collect2(expr, fact). Now assume expr is not a sum of products, but when seeing expr as a tree, there may be one or more sums of products inside. Then the factoring-out shall also be applied recursively to each 'inner' sum of products. More generally, the recursion shall also be made for a python list of expressions, i.e. each expression in the list shall be searched for possible factoring-out's. Apart from factoring out I do not want to apply any mathemetical rearrangements of the expression (that keep the expression value unchanged). With this I mean e.g. if you have var('a b c x y z') my_collect2( (a+b)*(x+y-1) + a+b + 2*c*(x+y) , x*y) I do not want to get (a+b+2*c) * (x+y) since this would first require to rearrange the expression to (a+b)(x+y) + 2c*(x+y) and then factor out (x+y). Further more, my_collect2 should also be callable with three arguments: my_collect2(expr, fact, subs_fact) should work like my_collect2(expr, fact) except that the each factored out term fact should be substituted by subs_fact, e.g. sage: var('x1 y1 y2 y3 z1 z2 x1y1') sage: mycollect2(x1*y1*y2 + y3*x1*y1, x1*y1, x1y1) Expected: (y2 + y3) * x1y1 It should be possible to write such a function my_collect2(), but since I'm new to sage it will probably be easier to do for someone with more experience. My idea is to recursively scan the expression tree for sums of products and then look if fact is a common factor of at least two of the products. I give some examples: sage: var('x1 y1 y2 y3 z1 z2') sage: my_collect2(x1*y1*y2 + y3*x1*y1, x1*y1) Expected: (y2 + y3) * x1*y1 sage: var('x1 y1 y2 y3 z1') sage: my_collect2(sin(exp( (x1+cos(y1))*z1*y2 + y3*(x1+cos(y1))*z1 )), (x1+cos(y1))*z1) Expected: sin(exp( (y2 + y3) * (x1+cos(y1))*z1 )) sage: my_collect2([z1*(x1+y1) + z2*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)], x1+y1) Expected: [(z1 + z2)*(x1+y1), (z1-1)*(x1+y1) + x1 + y1 + z2*(x1+y1)] Edit 10/04/2022: Some months ago I wrote a code that fulfills partly what I want. But as I'm beginner with sagemath, I'm sure that a lot of things in the code are too complicated, not optimal or even buggy. And the code was "in work", but then I didn't have the time to complete it in a way that I thought it was "finished" in some way. But I think just discarding the code wouldn't be good. So here it is: from sage.symbolic.operators import add_vararg, mul_vararg def is_exact_positive_integer_multiple_of(x, test_multiple): # returns None if x or test_multiple are not exact (containing floating point numbers). # Otherwise, returns test_multiple / x if sagemath can prove (by simplification, if necessary) that there # is a positive integer n so that test_multiple == n * x. If there is no such integer or there # is one but sagemath cannot prove that an integer n > 0 with test_multiple == n * x exists, # is_exact_positive_integer_multiple_of(x, test_multiple) returns None. # E.g. is_exact_positive_integer_multiple_of(3, 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi))) # could return 2 since 12*(cos(3/7*pi) - cos(2/7*pi) + cos(1/7*pi)) == 2*3, but # it could also return None since sagemath may not be able to prove that this holds. if not SR(x).is_exact() or not SR(test_multiple).is_exact(): return None x = SR(x).simplify_full() test_multiple = SR(test_multiple).simplify_full() if 0 == x: return None else: if ((test_multiple / x).is_integer() or \ (SR(x).is_constant() and SR(test_multiple).is_constant() and QQbar(test_multiple / x).is_integer())) \ and test_multiple >= x: return test_multiple / x else: return None def is_ratio_gerater_equal_one(x, test): # returns None if x or test are not exact (containing floating point numbers). # Otherwise, returns test / x if sagemath can prove (by simplification, if necessary) # that test / x >= 1. If test / x < 1 or sagemath cannot prove that test / x >= 1, # is_ratio_gerater_equal_one(x, test) returns None. if not SR(x).is_exact() or not SR(test).is_exact(): return None x = SR(x).simplify_full() test = SR(test).simplify_full() if x.is_zero(): return None else: if x.is_integer() and test.is_integer(): if (x > 0 and test > x-1) or (x < 0 and test < x+1): # You may think that this if-interrogation is redundant due to the following # interrogation for real x and test. # But it seems that sagemath cannot infer that z >= 1 if you declare the following: # var("y z") # assume(y, "integer") # assume(z, "integer", z>0) # Without the preceding interrogation # is_ratio_gerater_equal_one(y, y+z-1) # would yield False. return test / x else: return None elif test / x >= 1 or (SR(x).is_constant() and SR(test).is_constant() and QQbar(test / x) >= 1): return test / x else: return None def is_proven_integer(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_integer() or (SR(x).is_constant() and QQbar(x).is_integer())) def is_proven_nonnegative(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() if is_proven_real(x): rr = True elif x.operator() is operator.pow and is_proven_real(x.operands()[0]) and is_proven_real(x.operands()[1]) and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1])): rr = True else: rr = False return rr and bool(x >= 0) or (x.is_integer() and bool(x > -1)) or \ (SR(x).is_constant() and QQbar(x).is_real() and bool(QQbar(x) bool(QQbar(x) >= 0)) def is_proven_real(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool(x.is_real() or (SR(x).is_constant() and QQbar(x).is_real())) or \ (x.operator() is operator.pow and x.operands()[0].is_real() and x.operands()[1].is_real() and \ (is_proven_nonnegative(x.operands()[0]) or is_proven_integer(x.operands()[1]))) def is_proven_positive(x): if not SR(x).is_exact(): return False x = SR(x).simplify_full() return bool((is_proven_real(x) and x > 0) or (SR(x).is_constant() and QQbar(x) > 0)) def can_contract_consecutive_exponentiation(x, y, z): # returns True if a sufficient condition for (x ** y) ** z == x ** (y*z) could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_real(y) and is_proven_real(z)) def try_concatenate_consecutive_exponentiations(expr): if expr.operator() is not operator.pow: return expr, 1 else: base = expr.operands()[0] expn = expr.operands()[1] while base.operator() is operator.pow and \ can_contract_consecutive_exponentiation(base.operands()[0], base.operands()[1], expn): expn = base.operands()[1] * expn base = base.operands()[0] return base, expn def can_expand_power_of_product(x, y, z): # returns True if a sufficient condition for (x*y) ** z == x**z * y**z could be found, otherwise False. return is_proven_integer(z) or (is_proven_nonnegative(x) and is_proven_nonnegative(y) and is_proven_real(z)) def remove_power_of_factor_from_product_or_power(expr, factr, exponent, max_exponent = False, only_from_integer_powers = False): if max_exponent: return remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers) else: r, ee = remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers) return r, None if r is None else 1 def remove_power_of_factor_from_product_or_power_no_max_exponent(expr, factr, exponent, only_from_integer_powers): # factr must contain at least one variable, and this variable must also be in expr. # If expr == factr ** exponent, this function returns the pair 1, 1 . # Otherwise: 'expr' must be a product or an exponentiation (power function). # If 'factr' is a product or an exponentiation, look at all factors inside 'factr' # that contain a variable (no constant factors), otherwise look at 'factr' as a whole. Under some conditions, # remove_power_of_factor_from_product_or_power_no_max_exponent() returns the two values # expr / (factr ** exponent) , nn # otherwise, it returns None, None. These conditions are: # --- if only_from_integer_powers==True: # all factr ??????????????????????????????????????????????????????????????????? if 0 == exponent or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): p = expr intpow = None for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** exponent else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if intpow is None: intpow = nn elif only_from_integer_powers and nn != intpow: return None, None return p, nn if only_from_integer_powers else 0 op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 if only_from_integer_powers else 0 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and \ (is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expr.operands()[1])) is not None: # The second return value expr.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return factr ** (expr.operands()[1] - exponent), \ expr.operands()[1] / exponent if only_from_integer_powers else 0 elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 if only_from_integer_powers else 0 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr) and \ (is_exact_positive_integer_multiple_of(exponent, expn) if only_from_integer_powers \ else is_ratio_gerater_equal_one(exponent, expn)) is not None: without = factr ** (expn - exponent) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return value fac.operands()[1] / exponent is real (not complex, nonreal) # because of the tested is_exact_positive_integer_multiple_of() or is_ratio_gerater_equal_one() # condition. return without, fac.operands()[1] / exponent if only_from_integer_powers else 0 return None, None else: return None, None def remove_power_of_factor_from_product_or_power_max_exponent(expr, factr, exponent, only_from_integer_powers): if SR(exponent).is_zero() or SR(factr).is_constant() or SR(expr).is_constant(): # SR(factr).is_constant(): if factr is constant, i.e. if it contains no variables return None, None facop = factr.operator() if facop is operator.pow: factr, exponent = try_concatenate_consecutive_exponentiations(factr try_concatenate_consecutive_exponentiations(factr ** exponent) facop = factr.operator() if bool(facop == mul_vararg): min_abs_exp = None p = expr for fac in factr.operands(): if not can_expand_power_of_product(SR(factr / fac).simplify(), fac, exponent): return None, None if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables pass # needn't divide by constant since p will be discarded and re-calculated below else: p, nn = remove_power_of_factor_from_product_or_power_max_exponent(p, fac, exponent, only_from_integer_powers) if p is None: return None, None if min_abs_exp is None or abs(nn) < min_abs_exp: min_abs_exp = abs(nn) p = expr for fac in factr.operands(): if SR(fac).is_constant(): # if fac is constant, i.e. if it contains no variables p /= fac ** (min_abs_exp * exponent) else: p, nn = remove_power_of_factor_from_product_or_power_no_max_exponent(p, fac, min_abs_exp * exponent, only_from_integer_powers) if p is None: return None, None return p, min_abs_exp op = expr.operator() if op is None: if bool(expr == factr ** exponent): return 1, 1 else: return None, None elif op is operator.pow and expr.operands()[0]==factr and only_from_integer_powers and \ is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) is not None: # The second return ee is real (not complex, nonreal) because of the tested # is_exact_positive_integer_multiple_of() condition. ee = is_exact_positive_integer_multiple_of(exponent, expr.operands()[1]) return 1, ee elif op is operator.pow and expr.operands()[0]==factr and not only_from_integer_powers and \ is_ratio_gerater_equal_one(exponent, expr.operands()[1]) is not None: ee = is_ratio_gerater_equal_one(exponent, expr.operands()[1]) # The second return ee is real (not complex, nonreal) because of the tested # is_ratio_gerater_equal_one() condition. return factr ** (expr.operands()[1] - exponent * ee), ee elif bool(op == mul_vararg): without = None for i,fac in enumerate(expr.operands()): if bool(fac == factr ** exponent): without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, 1 elif fac.operator() is operator.pow: # If x and y are variables, x ** (-y) is converted to (x ** y) ** (-1) in sagemath. So check for consecutive exponentiations. base, expn = try_concatenate_consecutive_exponentiations(fac) if bool(base == factr): if only_from_integer_powers and is_exact_positive_integer_multiple_of(exponent, expn) is not None: ee = is_exact_positive_integer_multiple_of(exponent, expn) without = 1 for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 return without, ee elif not only_from_integer_powers and is_ratio_gerater_equal_one(exponent, expn) is not None: ee = is_ratio_gerater_equal_one(exponent, expn) without = factr ** (expn - exponent * ee) for j,fac2 in enumerate(expr.operands()): if j != i: without *= fac2 # The second return ee is real (not complex, nonreal) # because of the tested is_ratio_gerater_equal_one() condition. return without, ee return None, None else: return None, None def factor_out(expr, factr, *, recursive = True, all_integer_powers all_integer_powers = False, only_from_integer_powers = False, replace_factor_by = None): if type(expr) == list: return [factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr] if type(expr) == tuple: return tuple(factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr) if replace_factor_by is None: replace_factor_by = factr op = expr.operator() if op is None or op != add_vararg: if op is None or not recursive: return expr else: return op(*[factor_out(ex, factr, recursive recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in expr.operands()]) summands = expr.operands() factors_removed = [] if all_integer_powers: not_containing = summands while True: fac_removed = [] summands = not_containing max_exponent = None for p in summands: op = p.operator() if op == mul_vararg or (op is operator.pow and p.operands()[0]==factr and p.operands()[1] >= 1): without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, True, only_from_integer_powers) if without_factor is not None: if max_exponent is None or ee > max_exponent: max_exponent = ee if max_exponent is None: break not_containing = [] # must be after the preceeding if .. break statements for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append( op(*[factor_out(ex, factr, recursive recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, max_exponent, False, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: fac_removed.append(without_factor) else: fac_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): fac_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = True, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) if 0==len(fac_removed): break else: factors_removed.append( (fac_removed, max_exponent) ) return sum([sum(fm[0]) * (replace_factor_by ** fm[1]) for fm in factors_removed]) + sum(not_containing) else: not_containing = [] for p in summands: op = p.operator() if op == mul_vararg: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, False, only_from_integer_powers) if without_factor is None: if not recursive: not_containing.append(p) else: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) elif op is operator.pow: without_factor, ee = remove_power_of_factor_from_product_or_power(p, factr, 1, all_integer_powers, only_from_integer_powers) if without_factor is None: not_containing.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = False, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in p.operands()])) else: op = without_factor.operator() if op is None or not recursive: factors_removed.append(without_factor) else: factors_removed.append(op(*[factor_out(ex, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by) for ex in without_factor.operands()])) else: if bool(p == factr): factors_removed.append(1) else: not_containing.append(factor_out(p, factr, recursive = recursive, all_integer_powers = all_integer_powers, only_from_integer_powers = only_from_integer_powers, replace_factor_by = replace_factor_by)) return sum(factors_removed) * replace_factor_by + sum(not_containing) I give some examples how to use it: var('a b c x') expr = 7*a*x^(3/2) + 3*x^(5/2) + b*x^(5/2) + x^(3/2) - 5*x^(-9/2) - c*x^(-9/2) + a*x^5 + 2*x^5 + 7*x^3 + c*x^3 + c*x^(-3) + 9*x^(-3) factr = x print("factor_out(", expr, ", ", factr, ")") for allint in [False, True]: for onlyfromint in [False, True]: print("allintpow = ", allint, " onlyfromint = ", onlyfromint, " result = ", factor_out(expr, factr, all_integer_powers=allint, only_from_integer_powers=onlyfromint)) The output is: factor_out( a*x^5 + 2*x^5 + c*x^3 + b*x^(5/2) + 7*x^3 + 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) + c/x^3 + 9/x^3 - c/x^(9/2) - 5/x^(9/2) , x ) allintpow = False onlyfromint = False result = (a*x^4 + 2*x^4 + c*x^2 + b*x^(3/2) + 7*x^2 + 7*a*sqrt(x) + 3*x^(3/2) + sqrt(x))*x + c/x^3 + 9/x^3 - c/x^(9/2) - 5/x^(9/2) allintpow = False onlyfromint = True result = b*x^(5/2) + 7*a*x^(3/2) + 3*x^(5/2) + (a*x^4 + 2*x^4 + c*x^2 + 7*x^2)*x + x^(3/2) + c/x^3 + 9/x^3 - c/x^(9/2) - 5/x^(9/2) allintpow = True onlyfromint = False result = (a + 2)*x^5 + (c + 7)*x^3 + (b + 3)*x^(5/2) + (7*a + 1)*x^(3/2) + c/x^3 + 9/x^3 - c/x^(9/2) - 5/x^(9/2) allintpow = True onlyfromint = True result = (a + 2)*x^5 + (c + 7)*x^3 + b*x^(5/2) + 7*a*x^(3/2) + 3*x^(5/2) + x^(3/2) + c/x^3 + 9/x^3 - c/x^(9/2) - 5/x^(9/2) var('x1 y1 y2 y3 z1 z2') factor_out(x1*y1*y2 + y3*x1*y1, x1*y1) Output: x1*y1*(y2 + y3) var('a b x') factor_out((a+b)*x + x, x) Output: (a + b + 1)*x If you find any bugs or have other suggestions, let me know. 


 Copyright Sage, 2010. Some rights reserved under creative commons license. Content on this site is licensed under a Creative Commons Attribution Share Alike 3.0 license. about | faq | help | privacy policy | terms of service Powered by Askbot version 0.7.59 Please note: Askbot requires javascript to work properly, please enable javascript in your browser, here is how //IE fix to hide the red margin var noscript = document.getElementsByTagName('noscript')[0]; noscript.style.padding = '0px'; noscript.style.backgroundColor = 'transparent'; askbot['urls']['mark_read_message'] = '/s/messages/markread/'; askbot['urls']['get_tags_by_wildcard'] = '/s/get-tags-by-wildcard/'; askbot['urls']['get_tag_list'] = '/s/get-tag-list/'; askbot['urls']['follow_user'] = '/followit/follow/user/{{userId}}/'; askbot['urls']['unfollow_user'] = '/followit/unfollow/user/{{userId}}/'; askbot['urls']['user_signin'] = '/account/signin/'; askbot['urls']['getEditor'] = '/s/get-editor/'; askbot['urls']['apiGetQuestions'] = '/s/api/get_questions/'; askbot['urls']['ask'] = '/questions/ask/'; askbot['urls']['questions'] = '/questions/'; askbot['settings']['groupsEnabled'] = false; askbot['settings']['static_url'] = '/m/'; askbot['settings']['minSearchWordLength'] = 4; askbot['settings']['mathjaxEnabled'] = true; askbot['settings']['sharingSuffixText'] = ''; askbot['settings']['errorPlacement'] = 'after-label'; askbot['data']['maxCommentLength'] = 800; askbot['settings']['editorType'] = 'markdown'; askbot['settings']['commentsEditorType'] = 'rich\u002Dtext'; askbot['messages']['askYourQuestion'] = 'Ask Your Question'; askbot['messages']['acceptOwnAnswer'] = 'accept or unaccept your own answer'; askbot['messages']['followQuestions'] = 'follow questions'; askbot['settings']['allowedUploadFileTypes'] = [ "jpg", "jpeg", "gif", "bmp", "png", "tiff" ]; askbot['data']['haveFlashNotifications'] = true; askbot['data']['activeTab'] = 'questions'; askbot['settings']['csrfCookieName'] = 'asksage_csrf'; askbot['data']['searchUrl'] = ''; /*<![CDATA[*/ $('.mceStatusbar').remove();//a hack to remove the tinyMCE status bar$(document).ready(function(){ // focus input on the search bar endcomment var activeTab = askbot['data']['activeTab']; if (inArray(activeTab, ['users', 'questions', 'tags', 'badges'])) { var searchInput = $('#keywords'); } else if (activeTab === 'ask') { var searchInput =$('#id_title'); } else { var searchInput = undefined; animateHashes(); } var wasScrolled = $('#scroll-mem').val(); if (searchInput && !wasScrolled) { searchInput.focus(); putCursorAtEnd(searchInput); } var haveFullTextSearchTab = inArray(activeTab, ['questions', 'badges', 'ask']); var haveUserProfilePage =$('body').hasClass('user-profile-page'); if ((haveUserProfilePage || haveFullTextSearchTab) && searchInput && searchInput.length) { var search = new FullTextSearch(); askbot['controllers'] = askbot['controllers'] || {}; askbot['controllers']['fullTextSearch'] = search; search.setSearchUrl(askbot['data']['searchUrl']); if (activeTab === 'ask') { search.setAskButtonEnabled(false); } search.decorate(searchInput); } else if (activeTab === 'tags') { var search = new TagSearch(); search.decorate(searchInput); } if (askbot['data']['userIsAdminOrMod']) { $('body').addClass('admin'); } if (askbot['settings']['groupsEnabled']) { askbot['urls']['add_group'] = "/s/add-group/"; var group_dropdown = new GroupDropdown();$('.groups-dropdown').append(group_dropdown.getElement()); } var userRep = $('#userToolsNav .reputation'); if (userRep.length) { var showPermsTrigger = new ShowPermsTrigger(); showPermsTrigger.decorate(userRep); } }); if (askbot['data']['haveFlashNotifications']) {$('#validate_email_alert').click(function(){notify.close(true)}) notify.show(); } var langNav = $('.lang-nav'); if (langNav.length) { var nav = new LangNav(); nav.decorate(langNav); } /*]]>*/ if (typeof MathJax != 'undefined') { MathJax.Hub.Config({ extensions: ["tex2jax.js"], jax: ["input/TeX","output/HTML-CSS"], tex2jax: {inlineMath: [["$","$"],["\$","\$"]]} }); } else { console.log('Could not load MathJax'); } //todo - take this out into .js file$(document).ready(function(){ $('div.revision div[id^=rev-header-]').bind('click', function(){ var revId = this.id.substr(11); toggleRev(revId); }); lanai.highlightSyntax(); }); function toggleRev(id) { var arrow =$("#rev-arrow-" + id); var visible = arrow.attr("src").indexOf("hide") > -1; if (visible) { var image_path = '/m/default/media/images/expander-arrow-show.gif?v=19'; } else { var image_path = '/m/default/media/images/expander-arrow-hide.gif?v=19'; } image_path = image_path + "?v=19"; arrow.attr("src", image_path); \$("#rev-body-" + id).slideToggle("fast"); } for (url_name in askbot['urls']){ askbot['urls'][url_name] = cleanUrl(askbot['urls'][url_name]); }