util.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. """
  2. Several methods to simplify expressions involving unit objects.
  3. """
  4. from functools import reduce
  5. from collections.abc import Iterable
  6. from sympy.core.add import Add
  7. from sympy.core.containers import Tuple
  8. from sympy.core.mul import Mul
  9. from sympy.core.power import Pow
  10. from sympy.core.sorting import ordered
  11. from sympy.core.sympify import sympify
  12. from sympy.matrices.common import NonInvertibleMatrixError
  13. from sympy.physics.units.dimensions import Dimension
  14. from sympy.physics.units.prefixes import Prefix
  15. from sympy.physics.units.quantities import Quantity
  16. from sympy.utilities.iterables import sift
  17. def _get_conversion_matrix_for_expr(expr, target_units, unit_system):
  18. from sympy.matrices.dense import Matrix
  19. dimension_system = unit_system.get_dimension_system()
  20. expr_dim = Dimension(unit_system.get_dimensional_expr(expr))
  21. dim_dependencies = dimension_system.get_dimensional_dependencies(expr_dim, mark_dimensionless=True)
  22. target_dims = [Dimension(unit_system.get_dimensional_expr(x)) for x in target_units]
  23. canon_dim_units = [i for x in target_dims for i in dimension_system.get_dimensional_dependencies(x, mark_dimensionless=True)]
  24. canon_expr_units = {i for i in dim_dependencies}
  25. if not canon_expr_units.issubset(set(canon_dim_units)):
  26. return None
  27. seen = set()
  28. canon_dim_units = [i for i in canon_dim_units if not (i in seen or seen.add(i))]
  29. camat = Matrix([[dimension_system.get_dimensional_dependencies(i, mark_dimensionless=True).get(j, 0) for i in target_dims] for j in canon_dim_units])
  30. exprmat = Matrix([dim_dependencies.get(k, 0) for k in canon_dim_units])
  31. try:
  32. res_exponents = camat.solve(exprmat)
  33. except NonInvertibleMatrixError:
  34. return None
  35. return res_exponents
  36. def convert_to(expr, target_units, unit_system="SI"):
  37. """
  38. Convert ``expr`` to the same expression with all of its units and quantities
  39. represented as factors of ``target_units``, whenever the dimension is compatible.
  40. ``target_units`` may be a single unit/quantity, or a collection of
  41. units/quantities.
  42. Examples
  43. ========
  44. >>> from sympy.physics.units import speed_of_light, meter, gram, second, day
  45. >>> from sympy.physics.units import mile, newton, kilogram, atomic_mass_constant
  46. >>> from sympy.physics.units import kilometer, centimeter
  47. >>> from sympy.physics.units import gravitational_constant, hbar
  48. >>> from sympy.physics.units import convert_to
  49. >>> convert_to(mile, kilometer)
  50. 25146*kilometer/15625
  51. >>> convert_to(mile, kilometer).n()
  52. 1.609344*kilometer
  53. >>> convert_to(speed_of_light, meter/second)
  54. 299792458*meter/second
  55. >>> convert_to(day, second)
  56. 86400*second
  57. >>> 3*newton
  58. 3*newton
  59. >>> convert_to(3*newton, kilogram*meter/second**2)
  60. 3*kilogram*meter/second**2
  61. >>> convert_to(atomic_mass_constant, gram)
  62. 1.660539060e-24*gram
  63. Conversion to multiple units:
  64. >>> convert_to(speed_of_light, [meter, second])
  65. 299792458*meter/second
  66. >>> convert_to(3*newton, [centimeter, gram, second])
  67. 300000*centimeter*gram/second**2
  68. Conversion to Planck units:
  69. >>> convert_to(atomic_mass_constant, [gravitational_constant, speed_of_light, hbar]).n()
  70. 7.62963087839509e-20*hbar**0.5*speed_of_light**0.5/gravitational_constant**0.5
  71. """
  72. from sympy.physics.units import UnitSystem
  73. unit_system = UnitSystem.get_unit_system(unit_system)
  74. if not isinstance(target_units, (Iterable, Tuple)):
  75. target_units = [target_units]
  76. if isinstance(expr, Add):
  77. return Add.fromiter(convert_to(i, target_units, unit_system) for i in expr.args)
  78. expr = sympify(expr)
  79. if not isinstance(expr, Quantity) and expr.has(Quantity):
  80. expr = expr.replace(lambda x: isinstance(x, Quantity), lambda x: x.convert_to(target_units, unit_system))
  81. def get_total_scale_factor(expr):
  82. if isinstance(expr, Mul):
  83. return reduce(lambda x, y: x * y, [get_total_scale_factor(i) for i in expr.args])
  84. elif isinstance(expr, Pow):
  85. return get_total_scale_factor(expr.base) ** expr.exp
  86. elif isinstance(expr, Quantity):
  87. return unit_system.get_quantity_scale_factor(expr)
  88. return expr
  89. depmat = _get_conversion_matrix_for_expr(expr, target_units, unit_system)
  90. if depmat is None:
  91. return expr
  92. expr_scale_factor = get_total_scale_factor(expr)
  93. return expr_scale_factor * Mul.fromiter((1/get_total_scale_factor(u) * u) ** p for u, p in zip(target_units, depmat))
  94. def quantity_simplify(expr):
  95. """Return an equivalent expression in which prefixes are replaced
  96. with numerical values and all units of a given dimension are the
  97. unified in a canonical manner.
  98. Examples
  99. ========
  100. >>> from sympy.physics.units.util import quantity_simplify
  101. >>> from sympy.physics.units.prefixes import kilo
  102. >>> from sympy.physics.units import foot, inch
  103. >>> quantity_simplify(kilo*foot*inch)
  104. 250*foot**2/3
  105. >>> quantity_simplify(foot - 6*inch)
  106. foot/2
  107. """
  108. if expr.is_Atom or not expr.has(Prefix, Quantity):
  109. return expr
  110. # replace all prefixes with numerical values
  111. p = expr.atoms(Prefix)
  112. expr = expr.xreplace({p: p.scale_factor for p in p})
  113. # replace all quantities of given dimension with a canonical
  114. # quantity, chosen from those in the expression
  115. d = sift(expr.atoms(Quantity), lambda i: i.dimension)
  116. for k in d:
  117. if len(d[k]) == 1:
  118. continue
  119. v = list(ordered(d[k]))
  120. ref = v[0]/v[0].scale_factor
  121. expr = expr.xreplace({vi: ref*vi.scale_factor for vi in v[1:]})
  122. return expr
  123. def check_dimensions(expr, unit_system="SI"):
  124. """Return expr if units in addends have the same
  125. base dimensions, else raise a ValueError."""
  126. # the case of adding a number to a dimensional quantity
  127. # is ignored for the sake of SymPy core routines, so this
  128. # function will raise an error now if such an addend is
  129. # found.
  130. # Also, when doing substitutions, multiplicative constants
  131. # might be introduced, so remove those now
  132. from sympy.physics.units import UnitSystem
  133. unit_system = UnitSystem.get_unit_system(unit_system)
  134. def addDict(dict1, dict2):
  135. """Merge dictionaries by adding values of common keys and
  136. removing keys with value of 0."""
  137. dict3 = {**dict1, **dict2}
  138. for key, value in dict3.items():
  139. if key in dict1 and key in dict2:
  140. dict3[key] = value + dict1[key]
  141. return {key:val for key, val in dict3.items() if val != 0}
  142. adds = expr.atoms(Add)
  143. DIM_OF = unit_system.get_dimension_system().get_dimensional_dependencies
  144. for a in adds:
  145. deset = set()
  146. for ai in a.args:
  147. if ai.is_number:
  148. deset.add(())
  149. continue
  150. dims = []
  151. skip = False
  152. dimdict = {}
  153. for i in Mul.make_args(ai):
  154. if i.has(Quantity):
  155. i = Dimension(unit_system.get_dimensional_expr(i))
  156. if i.has(Dimension):
  157. dimdict = addDict(dimdict, DIM_OF(i))
  158. elif i.free_symbols:
  159. skip = True
  160. break
  161. dims.extend(dimdict.items())
  162. if not skip:
  163. deset.add(tuple(sorted(dims)))
  164. if len(deset) > 1:
  165. raise ValueError(
  166. "addends have incompatible dimensions: {}".format(deset))
  167. # clear multiplicative constants on Dimensions which may be
  168. # left after substitution
  169. reps = {}
  170. for m in expr.atoms(Mul):
  171. if any(isinstance(i, Dimension) for i in m.args):
  172. reps[m] = m.func(*[
  173. i for i in m.args if not i.is_number])
  174. return expr.xreplace(reps)