logarithmic.py 10 KB

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