def test_factor_and_dimension(): assert (3000, Dimension(1)) == Quantity._collect_factor_and_dimension(3000) assert (1001, length) == Quantity._collect_factor_and_dimension(meter + km) assert (2, length/time) == Quantity._collect_factor_and_dimension( meter/second + 36*km/(10*hour)) x, y = symbols('x y') assert (x + y/100, length) == Quantity._collect_factor_and_dimension( x*m + y*centimeter) cH = Quantity('cH', amount_of_substance/volume) pH = -log(cH) assert (1, volume/amount_of_substance) == Quantity._collect_factor_and_dimension( exp(pH)) v_w1 = Quantity('v_w1', length/time, S(3)/2*meter/second) v_w2 = Quantity('v_w2', length/time, 2*meter/second) expr = Abs(v_w1/2 - v_w2) assert (S(5)/4, length/time) == \ Quantity._collect_factor_and_dimension(expr) expr = S(5)/2*second/meter*v_w1 - 3000 assert (-(2996 + S(1)/4), Dimension(1)) == \ Quantity._collect_factor_and_dimension(expr) expr = v_w1**(v_w2/v_w1) assert ((S(3)/2)**(S(4)/3), (length/time)**(S(4)/3)) == \ Quantity._collect_factor_and_dimension(expr)
def _get_conversion_matrix_for_expr(expr, target_units, unit_system): from sympy 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 = {i for i in 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]) res_exponents = camat.solve_least_squares(exprmat, method=None) return res_exponents
def _get_conversion_matrix_for_expr(expr, target_units): from sympy import Matrix expr_dim = Dimension(Quantity.get_dimensional_expr(expr)) dim_dependencies = dimsys_default.get_dimensional_dependencies( expr_dim, mark_dimensionless=True) target_dims = [ Dimension(Quantity.get_dimensional_expr(x)) for x in target_units ] canon_dim_units = { i for x in target_dims for i in dimsys_default.get_dimensional_dependencies(x, mark_dimensionless=True) } canon_expr_units = {i for i in dim_dependencies} if not canon_expr_units.issubset(canon_dim_units): return None canon_dim_units = sorted(canon_dim_units) camat = Matrix([[ dimsys_default.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]) res_exponents = camat.solve_least_squares(exprmat, method=None) return res_exponents
def test_Dimension_error_regisration(): with warns_deprecated_sympy(): # tuple with more or less than two entries raises(IndexError, lambda: length._register_as_base_dim()) with warns_deprecated_sympy(): one = Dimension(1) raises(TypeError, lambda: one._register_as_base_dim())
def test_Dimension_error_regisration(): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=SymPyDeprecationWarning) # tuple with more or less than two entries raises(IndexError, lambda: length._register_as_base_dim()) one = Dimension(1) raises(TypeError, lambda: one._register_as_base_dim())
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)
def test_error_definition(): # tuple with more or less than two entries raises(TypeError, lambda: Dimension(("length", 1, 2))) raises(TypeError, lambda: Dimension(["length"])) # non-number power raises(TypeError, lambda: Dimension({"length": "a"})) # non-number with named argument raises(TypeError, lambda: Dimension({"length": (1, 2)}))
def test_quantity_postprocessing(): q1 = Quantity('q1', length*pressure**2*temperature/time) q2 = Quantity('q2', energy*pressure*temperature/(length**2*time)) assert q1 + q2 q = q1 + q2 Dq = Dimension(Quantity.get_dimensional_expr(q)) assert Dimension.get_dimensional_dependencies(Dq) == { 'length': -1, 'mass': 2, 'temperature': 1, 'time': -5, }
def test_abs(): v_w1 = Quantity('v_w1', length/time, meter/second) v_w2 = Quantity('v_w2', length/time, meter/second) v_w3 = Quantity('v_w3', length/time, meter/second) expr = v_w3 - Abs(v_w1 - v_w2) Dq = Dimension(Quantity.get_dimensional_expr(expr)) assert Dimension.get_dimensional_dependencies(Dq) == { 'length': 1, 'time': -1, } assert meter == sqrt(meter**2)
def test_Dimension_error_definition(): # tuple with more or less than two entries raises(TypeError, lambda: Dimension(("length", 1, 2))) raises(TypeError, lambda: Dimension(["length"])) # non-number power raises(TypeError, lambda: Dimension({"length": "a"})) # non-number with named argument raises(TypeError, lambda: Dimension({"length": (1, 2)})) # symbol should by Symbol or str raises(AssertionError, lambda: Dimension("length", symbol=1))
def dim_simplify(expr): """ NOTE: this function could be deprecated in the future. Simplify expression by recursively evaluating the dimension arguments. This function proceeds to a very rough dimensional analysis. It tries to simplify expression with dimensions, and it deletes all what multiplies a dimension without being a dimension. This is necessary to avoid strange behavior when Add(L, L) be transformed into Mul(2, L). """ if isinstance(expr, Dimension): return expr if isinstance(expr, Pow): return dim_simplify(expr.base)**dim_simplify(expr.exp) elif isinstance(expr, Function): return dim_simplify(expr.args[0]) elif isinstance(expr, Add): if (all(isinstance(arg, Dimension) for arg in expr.args) or all(arg.is_dimensionless for arg in expr.args if isinstance(arg, Dimension))): return reduce(lambda x, y: x.add(y), expr.args) else: raise ValueError("Dimensions cannot be added: %s" % expr) elif isinstance(expr, Mul): return Dimension( Mul(*[ dim_simplify(i).name for i in expr.args if isinstance(i, Dimension) ])) raise ValueError("Cannot be simplifed: %s", expr)
def check_dimensions(expr, unit_system="SI"): """Return expr if there are not unitless values added to dimensional quantities, 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) 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 for i in Mul.make_args(ai): if i.has(Quantity): i = Dimension(unit_system.get_dimensional_expr(i)) if i.has(Dimension): dims.extend(DIM_OF(i).items()) elif i.free_symbols: skip = True break if not skip: deset.add(tuple(sorted(dims))) if len(deset) > 1: raise ValueError( "addends have incompatible dimensions") # 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)
def test_Dimension_properties(): assert dimsys_SI.is_dimensionless(length) is False assert dimsys_SI.is_dimensionless(length/length) is True assert dimsys_SI.is_dimensionless(Dimension("undefined")) is False assert length.has_integer_powers(dimsys_SI) is True assert (length**(-1)).has_integer_powers(dimsys_SI) is True assert (length**1.5).has_integer_powers(dimsys_SI) is False
def test_properties(): assert length.is_dimensionless is False assert (length / length).is_dimensionless is True assert Dimension("undefined").is_dimensionless is True assert length.has_integer_powers is True assert (length**(-1)).has_integer_powers is True assert (length**1.5).has_integer_powers is False
def _get_conversion_matrix_for_expr(expr, target_units): from sympy import Matrix expr_dim = Dimension(Quantity.get_dimensional_expr(expr)) dim_dependencies = expr_dim.get_dimensional_dependencies(mark_dimensionless=True) target_dims = [Dimension(Quantity.get_dimensional_expr(x)) for x in target_units] canon_dim_units = {i for x in target_dims for i in x.get_dimensional_dependencies(mark_dimensionless=True)} canon_expr_units = {i for i in dim_dependencies} if not canon_expr_units.issubset(canon_dim_units): return None canon_dim_units = sorted(canon_dim_units) camat = Matrix([[i.get_dimensional_dependencies(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]) res_exponents = camat.solve_least_squares(exprmat, method=None) return res_exponents
def check_dimensions(expr): """Return expr if there are not unitless values added to dimensional quantities, else raise a ValueError.""" from sympy.solvers.solveset import _term_factors # 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 adds = expr.atoms(Add) DIM_OF = dimsys_default.get_dimensional_dependencies for a in adds: deset = set() for ai in a.args: if ai.is_number: deset.add(()) continue dims = [] skip = False for i in Mul.make_args(ai): if i.has(Quantity): i = Dimension(Quantity.get_dimensional_expr(i)) if i.has(Dimension): dims.extend(DIM_OF(i).items()) elif i.free_symbols: skip = True break if not skip: deset.add(tuple(sorted(dims))) if len(deset) > 1: raise ValueError( "addends have incompatible dimensions") # 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)
def test_factor_and_dimension(): assert (3000, Dimension(1)) == Quantity._collect_factor_and_dimension(3000) assert (1001, length) == Quantity._collect_factor_and_dimension(meter + km) assert (2, length / time) == Quantity._collect_factor_and_dimension(meter / second + 36 * km / (10 * hour)) x, y = symbols('x y') assert (x + y / 100, length) == Quantity._collect_factor_and_dimension(x * m + y * centimeter) cH = Quantity('cH') cH.set_dimension(amount_of_substance / volume) pH = -log(cH) assert (1, volume / amount_of_substance) == Quantity._collect_factor_and_dimension( exp(pH)) v_w1 = Quantity('v_w1') v_w2 = Quantity('v_w2') v_w1.set_dimension(length / time) v_w2.set_dimension(length / time) v_w1.set_scale_factor(Rational(3, 2) * meter / second) v_w2.set_scale_factor(2 * meter / second) expr = Abs(v_w1 / 2 - v_w2) assert (Rational(5, 4), length/time) == \ Quantity._collect_factor_and_dimension(expr) expr = Rational(5, 2) * second / meter * v_w1 - 3000 assert (-(2996 + Rational(1, 4)), Dimension(1)) == \ Quantity._collect_factor_and_dimension(expr) expr = v_w1**(v_w2 / v_w1) assert ((Rational(3, 2))**Rational(4, 3), (length/time)**Rational(4, 3)) == \ Quantity._collect_factor_and_dimension(expr)
def test_quantity_abs(): v_w1 = Quantity('v_w1') v_w2 = Quantity('v_w2') v_w3 = Quantity('v_w3') v_w1.set_dimension(length/time) v_w2.set_dimension(length/time) v_w3.set_dimension(length/time) v_w1.set_scale_factor(meter/second) v_w2.set_scale_factor(meter/second) v_w3.set_scale_factor(meter/second) expr = v_w3 - Abs(v_w1 - v_w2) Dq = Dimension(Quantity.get_dimensional_expr(expr)) assert dimsys_default.get_dimensional_dependencies(Dq) == { 'length': 1, 'time': -1, } assert meter == sqrt(meter**2)
def _convert_to(expr, quantity): if isinstance(expr, Add): return Add(*[_convert_to(i, quantity) for i in expr.args]) elif isinstance(expr, Mul): new_args = [_convert_to(i, quantity) for i in expr.args] edim = Dimension(Quantity.get_dimensional_expr(expr)) if edim == quantity.dimension: scale_factor_old = get_total_scale_factor(expr) return expr / get_units( expr) * scale_factor_old / quantity.scale_factor * quantity return Mul(*new_args) elif isinstance(expr, Pow): base = _convert_to(expr.base, quantity) edim = Dimension(Quantity.get_dimensional_expr(base))**expr.exp if edim == quantity.dimension: scale_factor_old = get_total_scale_factor(expr) return expr / get_units( expr) * scale_factor_old / quantity.scale_factor * quantity return base**expr.exp elif isinstance(expr, Quantity): edim = Dimension(Quantity.get_dimensional_expr(expr)) edep1 = edim.get_dimensional_dependencies() edep2 = quantity.dimension.get_dimensional_dependencies() if edim == quantity.dimension: return expr.scale_factor / quantity.scale_factor * quantity if set(edep1.keys()) == set(edep2.keys()): fracs = [ Rational(v1, v2) for v1, v2 in zip(edep1.values(), edep2.values()) ] powers = list(set(fracs)) if len(powers) == 1: return expr.scale_factor / quantity.scale_factor**powers[ 0] * quantity**powers[0] else: return expr return expr
def test_error_regisration(): # tuple with more or less than two entries raises(IndexError, lambda: length._register_as_base_dim()) one = Dimension(1) raises(TypeError, lambda: one._register_as_base_dim())
def test_str(): assert str(Dimension("length")) == "Dimension(length)" assert str(Dimension("length", "L")) == "Dimension(length, L)"
mebibyte = mebibytes = Quantity("mebibyte") mebibyte.set_dimension(information) mebibyte.set_scale_factor(mebi * byte) gibibyte = gibibytes = Quantity("gibibyte") gibibyte.set_dimension(information) gibibyte.set_scale_factor(gibi * byte) tebibyte = tebibytes = Quantity("tebibyte") tebibyte.set_dimension(information) tebibyte.set_scale_factor(tebi * byte) pebibyte = pebibytes = Quantity("pebibyte") pebibyte.set_dimension(information) pebibyte.set_scale_factor(pebi * byte) exbibyte = exbibytes = Quantity("exbibyte") exbibyte.set_dimension(information) exbibyte.set_scale_factor(exbi * byte) # check that scale factors are the right SI dimensions: for _scale_factor, _dimension in zip( Quantity.SI_quantity_scale_factors.values(), Quantity.SI_quantity_dimension_map.values()): dimex = Quantity.get_dimensional_expr(_scale_factor) if dimex != 1: if not dimsys_default.equivalent_dims(_dimension, Dimension(dimex)): raise ValueError("quantity value and dimension mismatch") del _scale_factor, _dimension
def test_Dimension_mul_div_exp(): assert 2 * length == length * 2 == length / 2 == length assert 2 / length == 1 / length x = Symbol('x') m = x * length assert m == length * x and m.is_Mul and set(m.args) == {x, length} d = x / length assert d == x * length**-1 and d.is_Mul and set(d.args) == {x, 1 / length} d = length / x assert d == length * x**-1 and d.is_Mul and set(d.args) == {1 / x, length} velo = length / time assert (length * length) == length**2 assert dimsys_SI.get_dimensional_dependencies(length * length) == { length: 2 } assert dimsys_SI.get_dimensional_dependencies(length**2) == {length: 2} assert dimsys_SI.get_dimensional_dependencies(length * time) == { length: 1, time: 1 } assert dimsys_SI.get_dimensional_dependencies(velo) == { length: 1, time: -1 } assert dimsys_SI.get_dimensional_dependencies(velo**2) == { length: 2, time: -2 } assert dimsys_SI.get_dimensional_dependencies(length / length) == {} assert dimsys_SI.get_dimensional_dependencies(velo / length * time) == {} assert dimsys_SI.get_dimensional_dependencies(length**-1) == {length: -1} assert dimsys_SI.get_dimensional_dependencies(velo**-1.5) == { length: -1.5, time: 1.5 } length_a = length**"a" assert dimsys_SI.get_dimensional_dependencies(length_a) == { length: Symbol("a") } assert dimsys_SI.get_dimensional_dependencies(length**pi) == {length: pi} assert dimsys_SI.get_dimensional_dependencies( length**(length / length)) == { length: Dimension(1) } raises(TypeError, lambda: dimsys_SI.get_dimensional_dependencies(length**length)) assert length != 1 assert length / length != 1 length_0 = length**0 assert dimsys_SI.get_dimensional_dependencies(length_0) == {} # issue 18738 a = Symbol('a') b = Symbol('b') c = sqrt(a**2 + b**2) c_dim = c.subs({a: length, b: length}) assert dimsys_SI.equivalent_dims(c_dim, length)
def convert_to(expr, quantity): """ Convert `expr` to the same expression with all of its units and quantities represented as factors of `quantity`, whenever the dimension is compatible. Examples ======== >>> from sympy.physics.units import speed_of_light, meter, gram, \ second, day, mile, newton, kilogram, inch, centimeter, atomic_mass_constant >>> from sympy.physics.units.definitions import kilometer >>> 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.66053904e-24*gram """ 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 expr.scale_factor return 1 def get_units(expr): if isinstance(expr, Mul): return reduce(lambda x, y: x * y, [get_units(i) for i in expr.args]) elif isinstance(expr, Pow): return get_units(expr.base)**expr.exp elif isinstance(expr, Quantity): return expr return 1 if isinstance(quantity, Quantity): backup_quantity = None else: backup_quantity = quantity quantity = Quantity("_temp", Dimension(Quantity.get_dimensional_expr(quantity)), get_total_scale_factor(quantity)) def _convert_to(expr, quantity): if isinstance(expr, Add): return Add(*[_convert_to(i, quantity) for i in expr.args]) elif isinstance(expr, Mul): new_args = [_convert_to(i, quantity) for i in expr.args] edim = Dimension(Quantity.get_dimensional_expr(expr)) if edim == quantity.dimension: scale_factor_old = get_total_scale_factor(expr) return expr / get_units( expr) * scale_factor_old / quantity.scale_factor * quantity return Mul(*new_args) elif isinstance(expr, Pow): base = _convert_to(expr.base, quantity) edim = Dimension(Quantity.get_dimensional_expr(base))**expr.exp if edim == quantity.dimension: scale_factor_old = get_total_scale_factor(expr) return expr / get_units( expr) * scale_factor_old / quantity.scale_factor * quantity return base**expr.exp elif isinstance(expr, Quantity): edim = Dimension(Quantity.get_dimensional_expr(expr)) edep1 = edim.get_dimensional_dependencies() edep2 = quantity.dimension.get_dimensional_dependencies() if edim == quantity.dimension: return expr.scale_factor / quantity.scale_factor * quantity if set(edep1.keys()) == set(edep2.keys()): fracs = [ Rational(v1, v2) for v1, v2 in zip(edep1.values(), edep2.values()) ] powers = list(set(fracs)) if len(powers) == 1: return expr.scale_factor / quantity.scale_factor**powers[ 0] * quantity**powers[0] else: return expr return expr res = _convert_to(expr, quantity) if backup_quantity: res = res.subs(quantity, backup_quantity) return res
def test_quantity_dimension_not_registered(): d = Dimension("someDimension") raises(ValueError, lambda: Quantity("q", d, 1)) raises(ValueError, lambda: Quantity("q", d/length, 1))