logarithmic.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. from itertools import combinations, product
  2. from .utils import find_variables, partition
  3. from ..node import ExpressionLeaf as L, OP_LOG, OP_ADD, OP_MUL, OP_POW, \
  4. Scope, log
  5. from ..possibilities import Possibility as P, MESSAGES
  6. from ..translate import _
  7. def match_constant_logarithm(node):
  8. """
  9. log_1(a) -> # raise ValueError for base 1
  10. log(1) -> 0
  11. log(a, a) -> 1 # Explicit possibility to prevent cycles
  12. log(a, a) -> log(a) / log(a) # -> 1
  13. """
  14. assert node.is_op(OP_LOG)
  15. raised, base = node
  16. if base == 1:
  17. raise ValueError('Logarithm with base 1 does not exist.')
  18. p = []
  19. if raised == 1:
  20. # log(1) -> 0
  21. p.append(P(node, logarithm_of_one))
  22. if raised == base:
  23. # log(a, a) -> 1
  24. p.append(P(node, base_equals_raised))
  25. # log(a, a) -> log(a) / log(a) # -> 1
  26. # TODO: When to do this except for this case?
  27. p.append(P(node, divide_same_base))
  28. return p
  29. def logarithm_of_one(root, args):
  30. """
  31. log(1) -> 0
  32. """
  33. raised, base = root
  34. return L(0).negate(root.negated)
  35. MESSAGES[logarithm_of_one] = _('Logarithm of one reduces to zero.')
  36. def base_equals_raised(root, args):
  37. """
  38. log(a, a) -> 1
  39. """
  40. return L(1).negate(root.negated)
  41. MESSAGES[base_equals_raised] = _('Logarithm {0} recuces to 1.')
  42. def divide_same_base(root, args):
  43. """
  44. log(a, b) -> log(a) / log(b)
  45. """
  46. raised, base = root
  47. return log(raised) / log(base)
  48. MESSAGES[divide_same_base] = _('Apply log_b(a) = log(a) / log(b) on {0}.')
  49. def match_add_logarithms(node):
  50. """
  51. log(a) + log(b) -> log(ab)
  52. -log(a) - log(b) -> -(log(a) + log(b)) # -> -log(ab)
  53. log(a) - log(b) -> log(a / b)
  54. -log(a) + log(b) -> log(b / a)
  55. """
  56. assert node.is_op(OP_ADD)
  57. p = []
  58. scope = Scope(node)
  59. logarithms = filter(lambda n: n.is_op(OP_LOG), scope)
  60. for log_a, log_b in combinations(logarithms, 2):
  61. # Compare base
  62. if log_a[1] != log_b[1]:
  63. continue
  64. a_negated = log_a.negated == 1
  65. b_negated = log_b.negated == 1
  66. if not log_a.negated and not log_b.negated:
  67. # log(a) + log(b) -> log(ab)
  68. p.append(P(node, add_logarithms, (scope, log_a, log_b)))
  69. elif a_negated and b_negated:
  70. # -log(a) - log(b) -> -(log(a) + log(b))
  71. p.append(P(node, expand_negations, (scope, log_a, log_b)))
  72. elif not log_a.negated and b_negated:
  73. # log(a) - log(b) -> log(a / b)
  74. p.append(P(node, subtract_logarithms, (scope, log_a, log_b)))
  75. elif a_negated and not log_b.negated:
  76. # -log(a) + log(b) -> log(b / a)
  77. p.append(P(node, subtract_logarithms, (scope, log_b, log_a)))
  78. return p
  79. def add_logarithms(root, args):
  80. """
  81. log(a) + log(b) -> log(ab)
  82. """
  83. scope, log_a, log_b = args
  84. a, base = log_a
  85. b = log_b[0]
  86. scope.replace(log_a, log(a * b, base=base))
  87. scope.remove(log_b)
  88. return scope.as_nary_node()
  89. MESSAGES[add_logarithms] = _('Apply log(a) + log(b) = log(ab).')
  90. #_('Combine logarithms with the same base: {2} and {3}.')
  91. def expand_negations(root, args):
  92. """
  93. -log(a) - log(b) -> -(log(a) + log(b)) # -> -log(ab)
  94. """
  95. scope, log_a, log_b = args
  96. scope.replace(log_a, -(+log_a + +log_b))
  97. scope.remove(log_b)
  98. return scope.as_nary_node()
  99. MESSAGES[expand_negations] = \
  100. _('Apply -log(a) - log(b) = -(log(a) + log(b)).')
  101. def subtract_logarithms(root, args):
  102. """
  103. log(a) - log(b) -> log(a / b)
  104. """
  105. scope, log_a, log_b = args
  106. a, base = log_a
  107. b = log_b[0]
  108. scope.replace(log_a, log(a / b, base=base))
  109. scope.remove(log_b)
  110. return scope.as_nary_node()
  111. MESSAGES[subtract_logarithms] = _('Apply log(a) - log(b) = log(a / b).')
  112. def match_raised_base(node):
  113. """
  114. g ^ log_g(a) -> a
  115. g ^ (blog_g(a)) -> g ^ log_g(a ^ b)
  116. """
  117. assert node.is_op(OP_POW)
  118. root, exponent = node
  119. if exponent.is_op(OP_LOG) and exponent[1] == root:
  120. return [P(node, raised_base, (exponent[0],))]
  121. p = []
  122. if exponent.is_op(OP_MUL):
  123. scope = Scope(exponent)
  124. is_matching_logarithm = lambda n: n.is_op(OP_LOG) and n[1] == root
  125. logs, others = partition(is_matching_logarithm, scope)
  126. for other, log in product(others, logs):
  127. # TODO: Give this function a high precedence
  128. p.append(P(node, factor_in_exponent_multiplicant,
  129. (scope, other, log)))
  130. return p
  131. def factor_in_exponent_multiplicant(root, args):
  132. r, e = root
  133. return r ** factor_in_multiplicant(e, args)
  134. def raised_base(root, args):
  135. """
  136. g ^ log_g(a) -> a
  137. """
  138. return args[0]
  139. MESSAGES[raised_base] = _('Apply g ^ log_g(a) = a on {0}.')
  140. def match_factor_out_exponent(node):
  141. """
  142. This match simplifies a power with a variable in it to a multiplication:
  143. log(a ^ b) -> blog(a)
  144. log(a ^ -b) -> log((a ^ b) ^ -1) # =>* -log(a ^ b)
  145. """
  146. assert node.is_op(OP_LOG)
  147. p = []
  148. if node[0].is_power():
  149. a, b = node[0]
  150. if b.negated:
  151. p.append(P(node, split_negative_exponent))
  152. p.append(P(node, factor_out_exponent))
  153. return p
  154. def split_negative_exponent(root, args):
  155. """
  156. log(a ^ -b) -> log((a ^ b) ^ -1) # =>* -log(a ^ b)
  157. """
  158. (a, b), base = root
  159. return log((a ** +b) ** -L(1), base=base)
  160. MESSAGES[split_negative_exponent] = \
  161. _('Split and factor out the negative exponent within logarithm {0}.')
  162. def factor_out_exponent(root, args):
  163. """
  164. log(a ^ b) -> blog(a)
  165. """
  166. (a, b), base = root
  167. return b * log(a, base=base)
  168. MESSAGES[factor_out_exponent] = _('Factor out exponent {0[0][0]} from {0}.')
  169. def match_factor_in_multiplicant(node):
  170. """
  171. Only bring a multiplicant inside a logarithms if both the multiplicant and
  172. the logaritm's content are constants. This will yield a new simplification
  173. of constants inside the logarithm.
  174. 2log(2) -> log(2 ^ 2) # -> log(4)
  175. 2log(2 / 4) -> log((2 / 4) ^ 2) # =>* log(1 / 4)
  176. """
  177. assert node.is_op(OP_MUL)
  178. scope = Scope(node)
  179. constants = filter(lambda n: n.is_int(), scope)
  180. logarithms = filter(lambda n: n.is_op(OP_LOG) \
  181. and not len(find_variables(n)), scope)
  182. p = []
  183. for constant, logarithm in product(constants, logarithms):
  184. p.append(P(node, factor_in_multiplicant, (scope, constant, logarithm)))
  185. return p
  186. def factor_in_multiplicant(root, args):
  187. """
  188. alog(b) -> log(b ^ a)
  189. """
  190. scope, a, log_b = args
  191. b, base = log_b
  192. scope.replace(a, log(b ** a, base=base))
  193. scope.remove(log_b)
  194. return scope.as_nary_node()
  195. MESSAGES[factor_in_multiplicant] = \
  196. _('Bring multiplicant {2} into {3} as the exponent of {3[0]}.')