| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- from itertools import combinations, product
- from .utils import find_variables, partition
- from ..node import ExpressionLeaf as L, OP_LOG, OP_ADD, OP_MUL, OP_POW, \
- Scope, log
- from ..possibilities import Possibility as P, MESSAGES
- from ..translate import _
- def match_constant_logarithm(node):
- """
- log_1(a) -> # raise ValueError for base 1
- log(1) -> 0
- log(a, a) -> 1 # Explicit possibility to prevent cycles
- log(a, a) -> log(a) / log(a) # -> 1
- """
- assert node.is_op(OP_LOG)
- raised, base = node
- if base == 1:
- raise ValueError('Logarithm with base 1 does not exist.')
- p = []
- if raised == 1:
- # log(1) -> 0
- p.append(P(node, logarithm_of_one))
- if raised == base:
- # log(a, a) -> 1
- p.append(P(node, base_equals_raised))
- # log(a, a) -> log(a) / log(a) # -> 1
- # TODO: When to do this except for this case?
- p.append(P(node, divide_same_base))
- return p
- def logarithm_of_one(root, args):
- """
- log(1) -> 0
- """
- raised, base = root
- return L(0).negate(root.negated)
- MESSAGES[logarithm_of_one] = _('Logarithm of one reduces to zero.')
- def base_equals_raised(root, args):
- """
- log(a, a) -> 1
- """
- return L(1).negate(root.negated)
- MESSAGES[base_equals_raised] = _('Logarithm {0} recuces to 1.')
- def divide_same_base(root, args):
- """
- log(a, b) -> log(a) / log(b)
- """
- raised, base = root
- return log(raised) / log(base)
- MESSAGES[divide_same_base] = _('Apply log_b(a) = log(a) / log(b) on {0}.')
- def match_add_logarithms(node):
- """
- log(a) + log(b) -> log(ab)
- -log(a) - log(b) -> -(log(a) + log(b)) # -> -log(ab)
- log(a) - log(b) -> log(a / b)
- -log(a) + log(b) -> log(b / a)
- """
- assert node.is_op(OP_ADD)
- p = []
- scope = Scope(node)
- logarithms = filter(lambda n: n.is_op(OP_LOG), scope)
- for log_a, log_b in combinations(logarithms, 2):
- # Compare base
- if log_a[1] != log_b[1]:
- continue
- a_negated = log_a.negated == 1
- b_negated = log_b.negated == 1
- if not log_a.negated and not log_b.negated:
- # log(a) + log(b) -> log(ab)
- p.append(P(node, add_logarithms, (scope, log_a, log_b)))
- elif a_negated and b_negated:
- # -log(a) - log(b) -> -(log(a) + log(b))
- p.append(P(node, expand_negations, (scope, log_a, log_b)))
- elif not log_a.negated and b_negated:
- # log(a) - log(b) -> log(a / b)
- p.append(P(node, subtract_logarithms, (scope, log_a, log_b)))
- elif a_negated and not log_b.negated:
- # -log(a) + log(b) -> log(b / a)
- p.append(P(node, subtract_logarithms, (scope, log_b, log_a)))
- return p
- def add_logarithms(root, args):
- """
- log(a) + log(b) -> log(ab)
- """
- scope, log_a, log_b = args
- a, base = log_a
- b = log_b[0]
- scope.replace(log_a, log(a * b, base=base))
- scope.remove(log_b)
- return scope.as_nary_node()
- MESSAGES[add_logarithms] = _('Apply log(a) + log(b) = log(ab).')
- #_('Combine logarithms with the same base: {2} and {3}.')
- def expand_negations(root, args):
- """
- -log(a) - log(b) -> -(log(a) + log(b)) # -> -log(ab)
- """
- scope, log_a, log_b = args
- scope.replace(log_a, -(+log_a + +log_b))
- scope.remove(log_b)
- return scope.as_nary_node()
- MESSAGES[expand_negations] = \
- _('Apply -log(a) - log(b) = -(log(a) + log(b)).')
- def subtract_logarithms(root, args):
- """
- log(a) - log(b) -> log(a / b)
- """
- scope, log_a, log_b = args
- a, base = log_a
- b = log_b[0]
- scope.replace(log_a, log(a / b, base=base))
- scope.remove(log_b)
- return scope.as_nary_node()
- MESSAGES[subtract_logarithms] = _('Apply log(a) - log(b) = log(a / b).')
- def match_raised_base(node):
- """
- g ^ log_g(a) -> a
- g ^ (blog_g(a)) -> g ^ log_g(a ^ b)
- """
- assert node.is_op(OP_POW)
- root, exponent = node
- if exponent.is_op(OP_LOG) and exponent[1] == root:
- return [P(node, raised_base, (exponent[0],))]
- p = []
- if exponent.is_op(OP_MUL):
- scope = Scope(exponent)
- is_matching_logarithm = lambda n: n.is_op(OP_LOG) and n[1] == root
- logs, others = partition(is_matching_logarithm, scope)
- for other, log in product(others, logs):
- # TODO: Give this function a high precedence
- p.append(P(node, factor_in_exponent_multiplicant,
- (scope, other, log)))
- return p
- def factor_in_exponent_multiplicant(root, args):
- r, e = root
- return r ** factor_in_multiplicant(e, args)
- def raised_base(root, args):
- """
- g ^ log_g(a) -> a
- """
- return args[0]
- MESSAGES[raised_base] = _('Apply g ^ log_g(a) = a on {0}.')
- def match_factor_out_exponent(node):
- """
- This match simplifies a power with a variable in it to a multiplication:
- log(a ^ b) -> blog(a)
- log(a ^ -b) -> log((a ^ b) ^ -1) # =>* -log(a ^ b)
- """
- assert node.is_op(OP_LOG)
- p = []
- if node[0].is_power():
- a, b = node[0]
- if b.negated:
- p.append(P(node, split_negative_exponent))
- p.append(P(node, factor_out_exponent))
- return p
- def split_negative_exponent(root, args):
- """
- log(a ^ -b) -> log((a ^ b) ^ -1) # =>* -log(a ^ b)
- """
- (a, b), base = root
- return log((a ** +b) ** -L(1), base=base)
- MESSAGES[split_negative_exponent] = \
- _('Split and factor out the negative exponent within logarithm {0}.')
- def factor_out_exponent(root, args):
- """
- log(a ^ b) -> blog(a)
- """
- (a, b), base = root
- return b * log(a, base=base)
- MESSAGES[factor_out_exponent] = _('Factor out exponent {0[0][0]} from {0}.')
- def match_factor_in_multiplicant(node):
- """
- Only bring a multiplicant inside a logarithms if both the multiplicant and
- the logaritm's content are constants. This will yield a new simplification
- of constants inside the logarithm.
- 2log(2) -> log(2 ^ 2) # -> log(4)
- 2log(2 / 4) -> log((2 / 4) ^ 2) # =>* log(1 / 4)
- """
- assert node.is_op(OP_MUL)
- scope = Scope(node)
- constants = filter(lambda n: n.is_int(), scope)
- logarithms = filter(lambda n: n.is_op(OP_LOG) \
- and not len(find_variables(n)), scope)
- p = []
- for constant, logarithm in product(constants, logarithms):
- p.append(P(node, factor_in_multiplicant, (scope, constant, logarithm)))
- return p
- def factor_in_multiplicant(root, args):
- """
- alog(b) -> log(b ^ a)
- """
- scope, a, log_b = args
- b, base = log_b
- scope.replace(a, log(b ** a, base=base))
- scope.remove(log_b)
- return scope.as_nary_node()
- MESSAGES[factor_in_multiplicant] = \
- _('Bring multiplicant {2} into {3} as the exponent of {3[0]}.')
|