123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- """
- Several methods to simplify expressions involving unit objects.
- """
- from functools import reduce
- from collections.abc import Iterable
- from typing import Optional
- from sympy import default_sort_key
- from sympy.core.add import Add
- from sympy.core.containers import Tuple
- from sympy.core.mul import Mul
- from sympy.core.power import Pow
- from sympy.core.sorting import ordered
- from sympy.core.sympify import sympify
- from sympy.matrices.common import NonInvertibleMatrixError
- from sympy.physics.units.dimensions import Dimension, DimensionSystem
- from sympy.physics.units.prefixes import Prefix
- from sympy.physics.units.quantities import Quantity
- from sympy.physics.units.unitsystem import UnitSystem
- from sympy.utilities.iterables import sift
- def _get_conversion_matrix_for_expr(expr, target_units, unit_system):
- from sympy.matrices.dense import Matrix
- dimension_system = unit_system.get_dimension_system()
- expr_dim = Dimension(unit_system.get_dimensional_expr(expr))
- dim_dependencies = dimension_system.get_dimensional_dependencies(expr_dim, mark_dimensionless=True)
- target_dims = [Dimension(unit_system.get_dimensional_expr(x)) for x in target_units]
- canon_dim_units = [i for x in target_dims for i in dimension_system.get_dimensional_dependencies(x, mark_dimensionless=True)]
- canon_expr_units = set(dim_dependencies)
- if not canon_expr_units.issubset(set(canon_dim_units)):
- return None
- seen = set()
- canon_dim_units = [i for i in canon_dim_units if not (i in seen or seen.add(i))]
- 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])
- exprmat = Matrix([dim_dependencies.get(k, 0) for k in canon_dim_units])
- try:
- res_exponents = camat.solve(exprmat)
- except NonInvertibleMatrixError:
- return None
- return res_exponents
- def convert_to(expr, target_units, unit_system="SI"):
- """
- Convert ``expr`` to the same expression with all of its units and quantities
- represented as factors of ``target_units``, whenever the dimension is compatible.
- ``target_units`` may be a single unit/quantity, or a collection of
- units/quantities.
- Examples
- ========
- >>> from sympy.physics.units import speed_of_light, meter, gram, second, day
- >>> from sympy.physics.units import mile, newton, kilogram, atomic_mass_constant
- >>> from sympy.physics.units import kilometer, centimeter
- >>> from sympy.physics.units import gravitational_constant, hbar
- >>> from sympy.physics.units import convert_to
- >>> convert_to(mile, kilometer)
- 25146*kilometer/15625
- >>> convert_to(mile, kilometer).n()
- 1.609344*kilometer
- >>> convert_to(speed_of_light, meter/second)
- 299792458*meter/second
- >>> convert_to(day, second)
- 86400*second
- >>> 3*newton
- 3*newton
- >>> convert_to(3*newton, kilogram*meter/second**2)
- 3*kilogram*meter/second**2
- >>> convert_to(atomic_mass_constant, gram)
- 1.660539060e-24*gram
- Conversion to multiple units:
- >>> convert_to(speed_of_light, [meter, second])
- 299792458*meter/second
- >>> convert_to(3*newton, [centimeter, gram, second])
- 300000*centimeter*gram/second**2
- Conversion to Planck units:
- >>> convert_to(atomic_mass_constant, [gravitational_constant, speed_of_light, hbar]).n()
- 7.62963087839509e-20*hbar**0.5*speed_of_light**0.5/gravitational_constant**0.5
- """
- from sympy.physics.units import UnitSystem
- unit_system = UnitSystem.get_unit_system(unit_system)
- if not isinstance(target_units, (Iterable, Tuple)):
- target_units = [target_units]
- if isinstance(expr, Add):
- return Add.fromiter(convert_to(i, target_units, unit_system)
- for i in expr.args)
- expr = sympify(expr)
- target_units = sympify(target_units)
- if not isinstance(expr, Quantity) and expr.has(Quantity):
- expr = expr.replace(lambda x: isinstance(x, Quantity),
- lambda x: x.convert_to(target_units, unit_system))
- def get_total_scale_factor(expr):
- if isinstance(expr, Mul):
- return reduce(lambda x, y: x * y,
- [get_total_scale_factor(i) for i in expr.args])
- elif isinstance(expr, Pow):
- return get_total_scale_factor(expr.base) ** expr.exp
- elif isinstance(expr, Quantity):
- return unit_system.get_quantity_scale_factor(expr)
- return expr
- depmat = _get_conversion_matrix_for_expr(expr, target_units, unit_system)
- if depmat is None:
- return expr
- expr_scale_factor = get_total_scale_factor(expr)
- return expr_scale_factor * Mul.fromiter(
- (1/get_total_scale_factor(u)*u)**p for u, p in
- zip(target_units, depmat))
- def quantity_simplify(expr, across_dimensions: bool=False, unit_system=None):
- """Return an equivalent expression in which prefixes are replaced
- with numerical values and all units of a given dimension are the
- unified in a canonical manner by default. `across_dimensions` allows
- for units of different dimensions to be simplified together.
- `unit_system` must be specified if `across_dimensions` is True.
- Examples
- ========
- >>> from sympy.physics.units.util import quantity_simplify
- >>> from sympy.physics.units.prefixes import kilo
- >>> from sympy.physics.units import foot, inch, joule, coulomb
- >>> quantity_simplify(kilo*foot*inch)
- 250*foot**2/3
- >>> quantity_simplify(foot - 6*inch)
- foot/2
- >>> quantity_simplify(5*joule/coulomb, across_dimensions=True, unit_system="SI")
- 5*volt
- """
- if expr.is_Atom or not expr.has(Prefix, Quantity):
- return expr
- # replace all prefixes with numerical values
- p = expr.atoms(Prefix)
- expr = expr.xreplace({p: p.scale_factor for p in p})
- # replace all quantities of given dimension with a canonical
- # quantity, chosen from those in the expression
- d = sift(expr.atoms(Quantity), lambda i: i.dimension)
- for k in d:
- if len(d[k]) == 1:
- continue
- v = list(ordered(d[k]))
- ref = v[0]/v[0].scale_factor
- expr = expr.xreplace({vi: ref*vi.scale_factor for vi in v[1:]})
- if across_dimensions:
- # combine quantities of different dimensions into a single
- # quantity that is equivalent to the original expression
- if unit_system is None:
- raise ValueError("unit_system must be specified if across_dimensions is True")
- unit_system = UnitSystem.get_unit_system(unit_system)
- dimension_system: DimensionSystem = unit_system.get_dimension_system()
- dim_expr = unit_system.get_dimensional_expr(expr)
- dim_deps = dimension_system.get_dimensional_dependencies(dim_expr, mark_dimensionless=True)
- target_dimension: Optional[Dimension] = None
- for ds_dim, ds_dim_deps in dimension_system.dimensional_dependencies.items():
- if ds_dim_deps == dim_deps:
- target_dimension = ds_dim
- break
- if target_dimension is None:
- # if we can't find a target dimension, we can't do anything. unsure how to handle this case.
- return expr
- target_unit = unit_system.derived_units.get(target_dimension)
- if target_unit:
- expr = convert_to(expr, target_unit, unit_system)
- return expr
- def check_dimensions(expr, unit_system="SI"):
- """Return expr if units in addends have the same
- base dimensions, else raise a ValueError."""
- # the case of adding a number to a dimensional quantity
- # is ignored for the sake of SymPy core routines, so this
- # function will raise an error now if such an addend is
- # found.
- # Also, when doing substitutions, multiplicative constants
- # might be introduced, so remove those now
- from sympy.physics.units import UnitSystem
- unit_system = UnitSystem.get_unit_system(unit_system)
- def addDict(dict1, dict2):
- """Merge dictionaries by adding values of common keys and
- removing keys with value of 0."""
- dict3 = {**dict1, **dict2}
- for key, value in dict3.items():
- if key in dict1 and key in dict2:
- dict3[key] = value + dict1[key]
- return {key:val for key, val in dict3.items() if val != 0}
- adds = expr.atoms(Add)
- DIM_OF = unit_system.get_dimension_system().get_dimensional_dependencies
- for a in adds:
- deset = set()
- for ai in a.args:
- if ai.is_number:
- deset.add(())
- continue
- dims = []
- skip = False
- dimdict = {}
- for i in Mul.make_args(ai):
- if i.has(Quantity):
- i = Dimension(unit_system.get_dimensional_expr(i))
- if i.has(Dimension):
- dimdict = addDict(dimdict, DIM_OF(i))
- elif i.free_symbols:
- skip = True
- break
- dims.extend(dimdict.items())
- if not skip:
- deset.add(tuple(sorted(dims, key=default_sort_key)))
- if len(deset) > 1:
- raise ValueError(
- "addends have incompatible dimensions: {}".format(deset))
- # clear multiplicative constants on Dimensions which may be
- # left after substitution
- reps = {}
- for m in expr.atoms(Mul):
- if any(isinstance(i, Dimension) for i in m.args):
- reps[m] = m.func(*[
- i for i in m.args if not i.is_number])
- return expr.xreplace(reps)
|