logarithmic.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. from itertools import combinations, product, ifilterfalse
  2. import math
  3. from .utils import find_variables, partition, divides, is_numeric_node
  4. from ..node import ExpressionLeaf as L, OP_LOG, OP_ADD, OP_MUL, OP_POW, \
  5. Scope, log, DEFAULT_LOGARITHM_BASE, E, OP_DIV
  6. from ..possibilities import Possibility as P, MESSAGES
  7. from ..translate import _
  8. def match_constant_logarithm(node):
  9. """
  10. log_1(a) -> # raise ValueError for base 1
  11. log(1) -> 0
  12. log(a, a) -> 1
  13. log(a, b) and b not in (10, e) -> log(a) / log(b)
  14. """
  15. assert node.is_op(OP_LOG)
  16. raised, base = node
  17. if base == 1:
  18. raise ValueError('Logarithm with base 1 does not exist.')
  19. p = []
  20. if raised == 1:
  21. # log(1) -> 0
  22. p.append(P(node, logarithm_of_one))
  23. if raised == base:
  24. # log(a, a) -> 1
  25. p.append(P(node, base_equals_raised))
  26. p.append(P(node, divide_same_base))
  27. elif base not in (DEFAULT_LOGARITHM_BASE, E):
  28. # log(a, b) -> log(a) / log(b)
  29. p.append(P(node, divide_same_base))
  30. return p
  31. def logarithm_of_one(root, args):
  32. """
  33. log(1) -> 0
  34. """
  35. raised, base = root
  36. return L(0).negate(root.negated)
  37. MESSAGES[logarithm_of_one] = _('Logarithm of one reduces to zero.')
  38. def base_equals_raised(root, args):
  39. """
  40. log(a, a) -> 1
  41. """
  42. return L(1).negate(root.negated)
  43. MESSAGES[base_equals_raised] = _('Logarithm {0} reduces to `1`.')
  44. def divide_same_base(root, args):
  45. """
  46. log(a, b) and b != 10 -> log(a) / log(b)
  47. """
  48. raised, base = root
  49. return log(raised) / log(base)
  50. MESSAGES[divide_same_base] = _('Apply `log_b(a) = log(a) / log(b)` on {0}.')
  51. def match_add_logarithms(node):
  52. """
  53. log(a) + log(b) and a,b in Z -> log(ab)
  54. -log(a) - log(b) and a,b in Z -> -(log(a) + log(b)) # -> -log(ab)
  55. log(a) - log(b) and a/b in Z -> log(a / b)
  56. -log(a) + log(b) and a/b in Z -> log(b / a)
  57. """
  58. assert node.is_op(OP_ADD)
  59. p = []
  60. scope = Scope(node)
  61. logarithms = filter(lambda n: n.is_op(OP_LOG), scope)
  62. for log_a, log_b in combinations(logarithms, 2):
  63. # Compare base
  64. (a, base_a), (b, base_b) = log_a, log_b
  65. if base_a != base_b or not a.is_numeric() \
  66. or not b.is_numeric(): # pragma: nocover
  67. continue
  68. a_negated = log_a.negated == 1
  69. b_negated = log_b.negated == 1
  70. if not log_a.negated and not log_b.negated:
  71. # log(a) + log(b) -> log(ab)
  72. p.append(P(node, add_logarithms, (scope, log_a, log_b)))
  73. elif a_negated and b_negated:
  74. # -log(a) - log(b) -> -(log(a) + log(b))
  75. p.append(P(node, expand_negations, (scope, log_a, log_b)))
  76. elif not log_a.negated and b_negated and divides(b.value, a.value):
  77. # log(a) - log(b) -> log(a / b)
  78. p.append(P(node, subtract_logarithms, (scope, log_a, log_b)))
  79. elif a_negated and not log_b.negated and divides(a.value, b.value):
  80. # -log(a) + log(b) -> log(b / a)
  81. p.append(P(node, subtract_logarithms, (scope, log_b, log_a)))
  82. return p
  83. def add_logarithms(root, args):
  84. """
  85. log(a) + log(b) -> log(ab)
  86. """
  87. scope, log_a, log_b = args
  88. a, base = log_a
  89. b = log_b[0]
  90. scope.replace(log_a, log(a * b, base=base))
  91. scope.remove(log_b)
  92. return scope.as_nary_node()
  93. MESSAGES[add_logarithms] = _('Apply `log(a) + log(b) = log(ab)`.')
  94. #_('Combine logarithms with the same base: {2} and {3}.')
  95. def expand_negations(root, args):
  96. """
  97. -log(a) - log(b) -> -(log(a) + log(b)) # -> -log(ab)
  98. """
  99. scope, log_a, log_b = args
  100. scope.replace(log_a, -(+log_a + +log_b))
  101. scope.remove(log_b)
  102. return scope.as_nary_node()
  103. MESSAGES[expand_negations] = \
  104. _('Apply `-log(a) - log(b) = -(log(a) + log(b))`.')
  105. def subtract_logarithms(root, args):
  106. """
  107. log(a) - log(b) -> log(a / b)
  108. """
  109. scope, log_a, log_b = args
  110. a, base = log_a
  111. b = log_b[0]
  112. scope.replace(log_a, log(a / b, base=base))
  113. scope.remove(log_b)
  114. return scope.as_nary_node()
  115. MESSAGES[subtract_logarithms] = _('Apply `log(a) - log(b) = log(a / b)`.')
  116. def match_raised_base(node):
  117. """
  118. g ^ log_g(a) -> a
  119. g ^ (b * log_g(a)) -> g ^ log_g(a ^ b)
  120. """
  121. assert node.is_op(OP_POW)
  122. root, exponent = node
  123. if exponent.is_op(OP_LOG) and exponent[1] == root:
  124. return [P(node, raised_base, (exponent[0],))]
  125. p = []
  126. if exponent.is_op(OP_MUL):
  127. scope = Scope(exponent)
  128. is_matching_logarithm = lambda n: n.is_op(OP_LOG) and n[1] == root
  129. logs, others = partition(is_matching_logarithm, scope)
  130. for other, log in product(others, logs):
  131. # Add this possibility so that a 'raised_base' possibility is
  132. # generated in the following iteration
  133. p.append(P(node, factor_in_exponent_multiplicant,
  134. (scope, other, log)))
  135. return p
  136. def factor_in_exponent_multiplicant(root, args):
  137. """
  138. g ^ (b * log_g(a)) -> g ^ log_g(a ^ b)
  139. """
  140. r, e = root
  141. return r ** factor_in_multiplicant(e, args)
  142. MESSAGES[factor_in_exponent_multiplicant] = \
  143. _('Bring {2} into {3} as exponent so that the power can be removed.')
  144. def raised_base(root, args):
  145. """
  146. g ^ log_g(a) -> a
  147. """
  148. return args[0]
  149. MESSAGES[raised_base] = _('Apply `g ^ (log_(g)(a)) = a` on {0}.')
  150. def match_factor_out_exponent(node):
  151. """
  152. This match simplifies a power with a variable in it to a multiplication:
  153. log(a ^ b) -> blog(a)
  154. log(a ^ -b) -> log((a ^ b) ^ -1) # =>* -log(a ^ b)
  155. log(b, a) and a ** y = b with y in Z -> log(a ^ y, a) # =>* y
  156. """
  157. assert node.is_op(OP_LOG)
  158. p = []
  159. exp, base = node
  160. if exp.is_power():
  161. a, b = exp
  162. if b.negated:
  163. p.append(P(node, split_negative_exponent))
  164. if a == base:
  165. p.append(P(node, factor_out_exponent_important))
  166. else:
  167. p.append(P(node, factor_out_exponent))
  168. elif exp.is_numeric() and not exp.negated:
  169. b, a = exp.value, base.value
  170. y = int(round(math.log(b, a)))
  171. if b == a ** y:
  172. p.append(P(node, make_raised_base, (y,)))
  173. return p
  174. def split_negative_exponent(root, args):
  175. """
  176. log(a ^ -b) -> log((a ^ b) ^ -1) # =>* -log(a ^ b)
  177. """
  178. (a, b), base = root
  179. return log((a ** +b) ** -L(1), base=base)
  180. MESSAGES[split_negative_exponent] = \
  181. _('Split and factor out the negative exponent within logarithm {0}.')
  182. def factor_out_exponent(root, args):
  183. """
  184. log(a ^ b) -> blog(a)
  185. """
  186. (a, b), base = root
  187. return b * log(a, base=base)
  188. MESSAGES[factor_out_exponent] = _('Factor out exponent {0[0][1]} from {0}.')
  189. def factor_out_exponent_important(root, args):
  190. return factor_out_exponent(root, args)
  191. MESSAGES[factor_out_exponent_important] = MESSAGES[factor_out_exponent]
  192. def make_raised_base(root, args):
  193. """
  194. log(b, a) and b ** y = a with y in Z -> log(a ^ y, a) # =>* y
  195. """
  196. exp, base = root
  197. y = L(args[0])
  198. return log(base.clone() ** y, base=base).negate(root.negated)
  199. MESSAGES[make_raised_base] = _('Write {0[0]} as a power of {0[1]}.')
  200. def match_factor_in_multiplicant(node):
  201. """
  202. Only bring a multiplicant inside a logarithm if both the multiplicant and
  203. the logaritm's content are constants. This will yield a new simplification
  204. of constants inside the logarithm.
  205. 2log(2) -> log(2 ^ 2) # -> log(4)
  206. 2log(2 / 4) -> log((2 / 4) ^ 2) # =>* log(1 / 4)
  207. """
  208. assert node.is_op(OP_MUL)
  209. scope = Scope(node)
  210. constants = filter(lambda n: n.is_int(), scope)
  211. logarithms = filter(lambda n: n.is_op(OP_LOG) \
  212. and not len(find_variables(n)), scope)
  213. p = []
  214. for constant, logarithm in product(constants, logarithms):
  215. p.append(P(node, factor_in_multiplicant, (scope, constant, logarithm)))
  216. return p
  217. def factor_in_multiplicant(root, args):
  218. """
  219. alog(b) -> log(b ^ a)
  220. """
  221. scope, a, log_b = args
  222. b, base = log_b
  223. scope.replace(a, log(b ** a, base=base))
  224. scope.remove(log_b)
  225. return scope.as_nary_node()
  226. MESSAGES[factor_in_multiplicant] = \
  227. _('Bring multiplicant {2} into {3} as the exponent of {3[0]}.')
  228. def match_expand_terms(node):
  229. """
  230. log(ab) and a not in Z -> log(a) + log(b)
  231. log(a / b) -> log(a) - log(b)
  232. """
  233. assert node.is_op(OP_LOG)
  234. exp = node[0]
  235. if exp.is_op(OP_MUL):
  236. scope = Scope(exp)
  237. return [P(node, expand_multiplication_terms, (scope, n)) \
  238. for n in ifilterfalse(is_numeric_node, scope)]
  239. if exp.is_op(OP_DIV):
  240. return [P(node, expand_division_terms)]
  241. return []
  242. def expand_multiplication_terms(root, args):
  243. """
  244. log(ab) and a not in Z -> log(a) + log(b)
  245. """
  246. scope, n = args
  247. scope.remove(n)
  248. base = root[1]
  249. addition = log(n, base=base) + log(scope.as_nary_node(), base=base)
  250. return addition.negate(root.negated)
  251. MESSAGES[expand_multiplication_terms] = _('Extract {2} from {0}.')
  252. def expand_division_terms(root, args):
  253. """
  254. log(a / b) -> log(a) - log(b)
  255. """
  256. division, base = root
  257. n, d = division
  258. addition = log(n.negate(division.negated), base=base) - log(d, base=base)
  259. return addition.negate(root.negated)
  260. MESSAGES[expand_division_terms] = _('Expand {0} to a subtraction.')