[yt-svn] commit/yt-3.0: 2 new changesets
commits-noreply at bitbucket.org
commits-noreply at bitbucket.org
Wed Mar 6 13:36:10 PST 2013
2 new commits in yt-3.0:
https://bitbucket.org/yt_analysis/yt-3.0/commits/1547e8bdcf78/
changeset: 1547e8bdcf78
branch: yt-3.0
user: caseywstark
date: 2012-11-09 18:14:56
summary: Adding symbolic units to yt. This is just a draft of the units module and tests for it. The tests are passing on my machine, but others should check.
affected #: 2 files
diff -r aeff6830301d7f126f40be62e09fd899e3816037 -r 1547e8bdcf7834ab673b6128553e3290d18b2200 yt/utilities/tests/test_units.py
--- /dev/null
+++ b/yt/utilities/tests/test_units.py
@@ -0,0 +1,378 @@
+"""
+Test symbolic unit handling.
+
+Author: Casey W. Stark <caseywstark at gmail.com>
+Affiliation: UC Berkeley
+
+License:
+ Copyright (C) 2012 Casey W. Stark. All Rights Reserved.
+
+ This file is part of yt.
+
+ yt is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+
+import nose
+from numpy.testing import assert_approx_equal
+from sympy import Symbol
+
+# base dimensions
+from yt.utilities.units import mass, length, time, temperature
+# functions
+from yt.utilities.units import get_conversion_factor, make_symbols_positive
+# classes
+from yt.utilities.units import Unit, UnitParseError
+# objects
+from yt.utilities.units import unit_symbols_dict, unit_prefixes
+
+
+def test_no_conflicting_symbols():
+ """
+ Check unit symbol definitions for conflicts.
+
+ """
+ full_set = set(unit_symbols_dict.keys())
+
+ # go through all possible prefix combos
+ for symbol in unit_symbols_dict.keys():
+ for prefix in unit_prefixes.keys():
+ new_symbol = "%s%s" % (prefix, symbol)
+
+ # test if we have seen this symbol
+ if new_symbol in full_set:
+ print "Duplicate symbol: %s" % new_symbol
+ assert False
+
+ full_set.add(new_symbol)
+
+
+def test_dimensionless():
+ """
+ Create dimensionless unit and check attributes.
+
+ """
+ u1 = Unit()
+
+ assert u1.is_dimensionless
+ assert u1.expr == 1
+ assert u1.cgs_value == 1
+ assert u1.dimensions == 1
+
+#
+# Start init tests
+#
+
+def test_create_from_string():
+ """
+ Create units with strings and check attributes.
+
+ """
+ from yt.utilities.units import energy
+
+ u1 = Unit("g * cm**2 * s**-2")
+ assert u1.dimensions == energy
+ assert u1.cgs_value == 1.0
+
+ # make sure order doesn't matter
+ u2 = Unit("cm**2 * s**-2 * g")
+ assert u2.dimensions == energy
+ assert u2.cgs_value == 1.0
+
+
+def test_create_from_expr():
+ """
+ Create units from sympy Exprs and check attributes.
+
+ """
+ pc_cgs = 3.08568e18
+ yr_cgs = 31536000
+
+ # Symbol expr
+ s1 = Symbol("pc", positive=True)
+ s2 = Symbol("yr", positive=True)
+ # Mul expr
+ s3 = s1 * s2
+ # Pow expr
+ s4 = s1**2 * s2**(-1)
+
+ u1 = Unit(s1)
+ u2 = Unit(s2)
+ u3 = Unit(s3)
+ u4 = Unit(s4)
+
+ assert u1.expr == s1
+ assert u2.expr == s2
+ assert u3.expr == s3
+ assert u4.expr == s4
+
+ assert u1.cgs_value == pc_cgs
+ assert u2.cgs_value == yr_cgs
+ assert u3.cgs_value == pc_cgs * yr_cgs
+ assert u4.cgs_value == pc_cgs**2 / yr_cgs
+
+ assert u1.dimensions == length
+ assert u2.dimensions == time
+ assert u3.dimensions == length * time
+ assert u4.dimensions == length**2 / time
+
+
+def test_create_with_duplicate_dimensions():
+ """
+ Create units with overlapping dimensions. Ex: km/Mpc.
+
+ """
+ from yt.utilities.units import power, rate
+
+ u1 = Unit("erg * s**-1")
+ u2 = Unit("km/s/Mpc")
+ km_cgs = 1e5
+ Mpc_cgs = 3.08568e24
+
+ print u2.expr
+ print u2.dimensions
+ print u2.cgs_value
+
+ assert u1.cgs_value == 1
+ assert u1.dimensions == power
+
+ assert u2.cgs_value == km_cgs / Mpc_cgs
+ assert u2.dimensions == rate
+
+
+def test_create_new_symbol():
+ """
+ Create unit with unknown symbol.
+
+ """
+ u1 = Unit("abc", cgs_value=42, dimensions=(mass/time))
+
+ assert u1.expr == Symbol("abc", positive=True)
+ assert u1.cgs_value == 42
+ assert u1.dimensions == mass / time
+
+
+def test_create_fail_on_unknown_symbol():
+ """
+ Fail to create unit with unknown symbol, without cgs_value and dimensions.
+
+ """
+ try:
+ u1 = Unit(Symbol("jigawatts"))
+ except UnitParseError:
+ pass
+ else:
+ assert False
+
+
+def test_create_fail_on_bad_symbol_type():
+ """
+ Fail to create unit with bad symbol type.
+
+ """
+ try:
+ u1 = Unit([1]) # something other than Expr and str
+ except UnitParseError:
+ pass
+ else:
+ assert False
+
+
+def test_create_fail_on_bad_dimensions_type():
+ """
+ Fail to create unit with bad dimensions type.
+
+ """
+ try:
+ u1 = Unit("a", cgs_value=1, dimensions="(mass)")
+ except UnitParseError:
+ pass
+ else:
+ assert False
+
+
+def test_create_fail_on_dimensions_content():
+ """
+ Fail to create unit with bad dimensions expr.
+
+ """
+ a = Symbol("a")
+
+ try:
+ u1 = Unit("a", cgs_value=1, dimensions=a)
+ except UnitParseError:
+ pass
+ else:
+ assert False
+
+
+def test_create_fail_on_cgs_value_type():
+ """
+ Fail to create unit with bad cgs_value type.
+
+ """
+ try:
+ u1 = Unit("a", cgs_value="a", dimensions=(mass/time))
+ except UnitParseError:
+ pass
+ else:
+ assert False
+
+#
+# End init tests
+#
+
+def test_string_representation():
+ """
+ Check unit string representation.
+
+ """
+ pc = Unit("pc")
+ Myr = Unit("Myr")
+ speed = pc / Myr
+ dimensionless = Unit()
+
+ assert str(pc) == "pc"
+ assert str(Myr) == "Myr"
+ assert str(speed) == "pc/Myr"
+ assert repr(speed) == "pc/Myr"
+ assert str(dimensionless) == "(dimensionless)"
+
+#
+# Start operation tests
+#
+
+def test_multiplication():
+ """
+ Multiply two units.
+
+ """
+ msun_cgs = 1.98892e33
+ pc_cgs = 3.08568e18
+
+ # Create symbols
+ msun_sym = Symbol("Msun", positive=True)
+ pc_sym = Symbol("pc", positive=True)
+ s_sym = Symbol("s", positive=True)
+
+ # Create units
+ u1 = Unit("Msun")
+ u2 = Unit("pc")
+
+ # Mul operation
+ u3 = u1 * u2
+
+ assert u3.expr == msun_sym * pc_sym
+ assert u3.cgs_value == msun_cgs * pc_cgs
+ assert u3.dimensions == mass * length
+
+ # Pow and Mul operations
+ u4 = Unit("pc**2")
+ u5 = Unit("Msun * s")
+
+ u6 = u4 * u5
+
+ assert u6.expr == pc_sym**2 * msun_sym * s_sym
+ assert u6.cgs_value == pc_cgs**2 * msun_cgs
+ assert u6.dimensions == length**2 * mass * time
+
+
+def test_division():
+ """
+ Divide two units.
+
+ """
+ pc_cgs = 3.08568e18
+ km_cgs = 1e5
+
+ # Create symbols
+ pc_sym = Symbol("pc", positive=True)
+ km_sym = Symbol("km", positive=True)
+ s_sym = Symbol("s", positive=True)
+
+ # Create units
+ u1 = Unit("pc")
+ u2 = Unit("km * s")
+
+ u3 = u1 / u2
+
+ assert u3.expr == pc_sym / (km_sym * s_sym)
+ assert u3.cgs_value == pc_cgs / km_cgs
+ assert u3.dimensions == 1 / time
+
+
+def test_power():
+ """
+ Take units to some power.
+
+ """
+ from sympy import nsimplify
+
+ pc_cgs = 3.08568e18
+ mK_cgs = 1e-3
+ u1_dims = mass * length**2 * time**-3 * temperature**4
+ u1 = Unit("g * pc**2 * s**-3 * mK**4")
+
+ u2 = u1**2
+
+ assert u2.dimensions == u1_dims**2
+ assert u2.cgs_value == (pc_cgs**2 * mK_cgs**4)**2
+
+ u3 = u1**(-1.0/3)
+
+ assert u3.dimensions == nsimplify(u1_dims**(-1.0/3))
+ assert u3.cgs_value == (pc_cgs**2 * mK_cgs**4)**(-1.0/3)
+
+
+def test_equality():
+ """
+ Check unit equality with different symbols, but same dimensions and cgs_value.
+
+ """
+ u1 = Unit("km * s**-1")
+ u2 = Unit("m * ms**-1")
+
+ assert u1 == u2
+
+#
+# End operation tests.
+#
+
+def test_cgs_equivalent():
+ """
+ Check cgs equivalent of a unit.
+
+ """
+ Msun_cgs = 1.98892e33
+ Mpc_cgs = 3.08568e24
+
+ u1 = Unit("Msun * Mpc**-3")
+ u2 = Unit("g * cm**-3")
+ u3 = u1.get_cgs_equivalent()
+
+ assert u2.expr == u3.expr
+ assert u2 == u3
+
+ assert_approx_equal( u1.cgs_value, Msun_cgs / Mpc_cgs**3, significant=16)
+ assert u2.cgs_value == 1
+ assert u3.cgs_value == 1
+
+ mass_density = mass / length**3
+
+ assert u1.dimensions == mass_density
+ assert u2.dimensions == mass_density
+ assert u3.dimensions == mass_density
+
+ assert_approx_equal( get_conversion_factor(u1, u3), Msun_cgs / Mpc_cgs**3,
+ significant=16 )
diff -r aeff6830301d7f126f40be62e09fd899e3816037 -r 1547e8bdcf7834ab673b6128553e3290d18b2200 yt/utilities/units.py
--- /dev/null
+++ b/yt/utilities/units.py
@@ -0,0 +1,448 @@
+"""
+Symbolic unit handling.
+
+Author: Casey W. Stark <caseywstark at gmail.com>
+Affiliation: UC Berkeley
+
+License:
+ Copyright (C) 2012 Casey W. Stark. All Rights Reserved.
+
+ This file is part of yt.
+
+ yt is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+
+from sympy import Expr, Mul, Number, Pow, Rational, Symbol
+from sympy import nsimplify, posify, sympify
+from sympy.parsing.sympy_parser import parse_expr
+
+# The base dimensions
+mass = Symbol("(mass)", positive=True)
+length = Symbol("(length)", positive=True)
+time = Symbol("(time)", positive=True)
+temperature = Symbol("(temperature)", positive=True)
+base_dimensions = [mass, length, time, temperature]
+
+### Misc. dimensions
+rate = 1 / time
+
+velocity = length / time
+acceleration = length / time**2
+jerk = length / time**3
+snap = length / time**4
+crackle = length / time**5
+pop = length / time**6
+
+momentum = mass * velocity
+force = mass * acceleration
+energy = force * length
+power = energy / time
+charge = (energy * length)**Rational(1, 2) # proper 1/2 power
+
+electric_field = charge / length**2
+magnetic_field = electric_field
+
+# Dictionary that holds information of known unit symbols.
+# The key is the symbol, the value is a tuple with the conversion factor to cgs,
+# and the dimensionality.
+unit_symbols_dict = {
+ # base
+ "g": (1.0, mass),
+ #"cm": (1.0, length), # duplicate with meter below...
+ "s": (1.0, time),
+ "K": (1.0, temperature),
+
+ # other cgs
+ "dyne": (1.0, force),
+ "erg": (1.0, energy),
+ "esu": (1.0, charge),
+
+ # some SI
+ "m": (1.0e2, length),
+ "J": (1.0e7, energy),
+ "Hz": (1.0, rate),
+
+ # times
+ "min": (60.0, time),
+ "hr": (3600.0, time),
+ "day": (86400.0, time),
+ "yr": (31536000.0, time),
+
+ # Solar units
+ "Msun": (1.98892e33, mass),
+ "Rsun": (6.96e10, length),
+ "Lsun": (3.9e33, power),
+ "Tsun": (5870.0, temperature),
+
+ # astro distances
+ "AU": (1.49598e13, length),
+ "ly": (9.46053e17, length),
+ "pc": (3.08568e18, length),
+
+ # other astro
+ "H_0": (2.3e-18, rate), # check cf
+
+ # other energy units
+ "eV": (1.6021766e-12, energy),
+
+ # electric stuff
+ "gauss": (1.0, magnetic_field),
+}
+
+# This dictionary formatting from magnitude package, credit to Juan Reyero.
+unit_prefixes = {
+ 'Y': 1e24, # yotta
+ 'Z': 1e21, # zetta
+ 'E': 1e18, # exa
+ 'P': 1e15, # peta
+ 'T': 1e12, # tera
+ 'G': 1e9, # giga
+ 'M': 1e6, # mega
+ 'k': 1e3, # kilo
+ 'd': 1e1, # deci
+ 'c': 1e-2, # centi
+ 'm': 1e-3, # mili
+ 'u': 1e-6, # micro
+ 'n': 1e-9, # nano
+ 'p': 1e-12, # pico
+ 'f': 1e-15, # femto
+ 'a': 1e-18, # atto
+ 'z': 1e-21, # zepto
+ 'y': 1e-24, # yocto
+}
+
+
+class UnitParseError(Exception):
+ pass
+class UnitOperationError(Exception):
+ pass
+
+
+class Unit(Expr):
+ """
+ A symbolic unit, using sympy functionality. We only add "dimensions" so that
+ sympy understands relations between different units.
+
+ """
+
+ is_positive = True # make sqrt(m**2) --> m
+ is_commutative = True
+ is_number = False
+
+ __slots__ = ["expr", "cgs_value", "dimensions", "is_atomic"]
+
+ def __new__(cls, unit_expr=sympify(1), cgs_value=None, dimensions=None,
+ **assumptions):
+ """
+ Create a new unit. May be an atomic unit (like a gram) or a combination
+ of other units (like g / cm**3).
+
+ Parameters
+ ----------
+ unit_expr : string or sympy.core.expr.Expr
+ The unit symbol expression.
+ cgs_value : float
+ This unit's value in cgs.
+ dimensions : sympy.core.expr.Expr
+ An expression representing the dimensionality of this unit. This
+ should be a product (sympy.core.mul.Mul object) of mass, length,
+ time, and temperature objects to various powers.
+
+ """
+ # if we have a string, parse into an expression
+ if isinstance(unit_expr, str):
+ unit_expr = parse_expr(unit_expr)
+
+ if not isinstance(unit_expr, Expr):
+ raise UnitParseError("Unit representation must be a string or sympy Expr. %s is a %s." % (unit_expr, type(unit_expr)))
+ # done with argument checking...
+
+ # sympify, make positive symbols, and nsimplify the expr
+ unit_expr = sympify(unit_expr)
+ unit_expr = make_symbols_positive(unit_expr)
+ unit_expr = nsimplify(unit_expr)
+
+ # see if the unit is atomic.
+ is_atomic = False
+ if isinstance(unit_expr, Symbol):
+ is_atomic = True
+
+ # Did the user supply cgs_value and dimensions?
+ if cgs_value and not dimensions or dimensions and not cgs_value:
+ raise Exception("If you provide cgs_vale or dimensions, you must provide both. cgs_value is %s, dimensions is %s." % (cgs_value, dimensions))
+
+ if cgs_value and dimensions:
+ # check that cgs_value is a float or can be converted to one
+ try:
+ cgs_value = float(cgs_value)
+ except ValueError:
+ raise UnitParseError("Please provide a float for the cgs_value kwarg. I got '%s'." % cgs_value)
+ # check that dimensions is valid
+ dimensions = verify_dimensions(dimensions)
+ # save the values
+ this_cgs_value, this_dimensions = cgs_value, dimensions
+
+ else: # lookup the unit symbols
+ this_cgs_value, this_dimensions = \
+ get_unit_data_from_expr(unit_expr)
+
+ # Trick to get dimensions powers as Rationals
+ this_dimensions = nsimplify(this_dimensions)
+
+ # create obj with superclass construct
+ obj = Expr.__new__(cls, **assumptions)
+
+ # attach attributes to obj
+ obj.expr = unit_expr
+ obj.is_atomic = is_atomic
+ obj.cgs_value = this_cgs_value
+ obj.dimensions = this_dimensions
+
+ # return `obj` so __init__ can handle it.
+ return obj
+
+ ### some sympy conventions I guess
+ def __getnewargs__(self):
+ return (self.expr, self.is_atomic, self.cgs_value, self.dimensions)
+
+ def __hash__(self):
+ return super(Unit, self).__hash__()
+
+ def _hashable_content(self):
+ return (self.expr, self.is_atomic, self.cgs_value, self.dimensions)
+ ### end sympy conventions
+
+ def __repr__(self):
+ if self.expr == 1:
+ return "(dimensionless)"
+ return str(self.expr)
+
+ def __str__(self):
+ if self.expr == 1:
+ return "(dimensionless)"
+ return str(self.expr)
+
+ # for sympy.printing
+ def _sympystr(self, *args):
+ return str(self.expr)
+
+ ### override sympy operations
+ def __mul__(self, u):
+ """ Multiply Unit with u (Unit object). """
+ if not isinstance(u, Unit):
+ raise UnitOperationError("Tried to multiply Unit object with '%s'. This behavior is undefined." % u)
+
+ return Unit(self.expr * u.expr, self.cgs_value * u.cgs_value,
+ self.dimensions * u.dimensions)
+
+ def __div__(self, u):
+ """ Divide Unit by u (Unit object). """
+ if not isinstance(u, Unit):
+ raise UnitOperationError("Tried to divide Unit object by '%s'. This behavior is undefined." % u)
+
+ return Unit(self.expr / u.expr, self.cgs_value / u.cgs_value,
+ self.dimensions / u.dimensions)
+
+ def __pow__(self, p):
+ """ Take Unit to power p (float). """
+ try:
+ p = sympify(p)
+ except ValueError:
+ raise UnitOperationError("Tried to take Unit object to the power '%s'. I could not cast this to a float." % p)
+
+ return Unit(self.expr**p, self.cgs_value**p, self.dimensions**p)
+
+ ### Comparison operators
+ def same_dimensions_as(self, other_unit):
+ """ Test if dimensions are the same. """
+ return (self.dimensions / other_unit.dimensions) == 1
+
+ def __eq__(self, u):
+ """ Test unit equality. """
+ if not isinstance(u, Unit):
+ raise UnitOperationError("Tried to test equality between Unit object and '%s'. This behavior is undefined." % u)
+
+ return (self.cgs_value == u.cgs_value and self.dimensions == u.dimensions)
+
+ @property
+ def is_dimensionless(self):
+ return self.dimensions == 1
+
+ def get_cgs_equivalent(self):
+ """ Create and return dimensionally-equivalent cgs units. """
+ cgs_units_string = "g**(%s) * cm**(%s) * s**(%s) * K**(%s)" % \
+ (self.dimensions.expand().as_coeff_exponent(mass)[1],
+ self.dimensions.expand().as_coeff_exponent(length)[1],
+ self.dimensions.expand().as_coeff_exponent(time)[1],
+ self.dimensions.expand().as_coeff_exponent(temperature)[1])
+ return Unit(cgs_units_string, 1, self.dimensions)
+
+
+def make_symbols_positive(expr):
+ """
+ Grabs all symbols from expr, makes new positive symbols with the same names,
+ and substitutes them back into the expression.
+
+ """
+ expr_symbols = expr.atoms(Symbol) # grab all symbols
+
+ # Replace one at a time
+ for s in expr_symbols:
+ # replace this symbol with a positive version
+ expr = expr.subs(s, Symbol(s.name, positive=True))
+
+ return expr
+
+
+# @todo: simpler method that doesn't use recursion would be better...
+# We could check if dimensions.atoms are all numbers or symbols, but we should
+# check the functions also...
+def verify_dimensions(d):
+ """
+ Make sure that `d` is a valid dimension expression. It must consist of only
+ the base dimension symbols, to powers, multiplied together. If valid, return
+ the simplified expression. If not, raise an Exception.
+
+ """
+ # in the case of a Number of Symbol, we can just return
+ if isinstance(d, Number):
+ return d
+ elif isinstance(d, Symbol):
+ if d in base_dimensions:
+ return d
+ else:
+ raise UnitParseError("dimensionality expression contains an unknown symbol '%s'." % d)
+
+ # validate args of a Pow or Mul separately
+ elif isinstance(d, Pow):
+ return verify_dimensions(d.args[0])**verify_dimensions(d.args[1])
+
+ elif isinstance(d, Mul):
+ total_mul = 1
+ for arg in d.args:
+ total_mul *= verify_dimensions(arg)
+ return total_mul
+
+ # should never get here
+ raise UnitParseError("Bad dimensionality expression '%s'." % d)
+
+
+def get_unit_data_from_expr(unit_expr):
+ """
+ Gets total cgs_value and dimensions from a valid unit expression.
+
+ """
+ # The simplest case first
+ if isinstance(unit_expr, Unit):
+ return (unit_expr.cgs_value, unit_expr.dimensions)
+
+ # Now for the sympy possibilities
+ if isinstance(unit_expr, Symbol):
+ return lookup_unit_symbol(str(unit_expr))
+
+ if isinstance(unit_expr, Number):
+ return (1, 1)
+
+ if isinstance(unit_expr, Pow):
+ unit_data = get_unit_data_from_expr(unit_expr.args[0])
+ power = unit_expr.args[1]
+ return (unit_data[0]**power, unit_data[1]**power)
+
+ if isinstance(unit_expr, Mul):
+ cgs_value = 1
+ dimensions = 1
+ for expr in unit_expr.args:
+ unit_data = get_unit_data_from_expr(expr)
+ cgs_value *= unit_data[0]
+ dimensions *= unit_data[1]
+
+ return (cgs_value, dimensions)
+
+ raise UnitParseError("Cannot parse for unit data from '%s'. Please supply an expression of only Unit, Symbol, Pow, and Mul objects." % str(unit_expr))
+
+
+def lookup_unit_symbol(symbol_str):
+ """ Searches for the unit data typle corresponding to the given symbol. """
+
+ if symbol_str in unit_symbols_dict:
+ # lookup successful, return the tuple directly
+ return unit_symbols_dict[symbol_str]
+
+ # could still be a known symbol with a prefix
+ possible_prefix = symbol_str[0]
+ if possible_prefix in unit_prefixes:
+ # the first character could be a prefix, check the rest of the symbol
+ symbol_wo_prefix = symbol_str[1:]
+
+ if symbol_wo_prefix in unit_symbols_dict:
+ # lookup successful, it's a symbol with a prefix
+ unit_data = unit_symbols_dict[symbol_wo_prefix]
+ prefix_value = unit_prefixes[possible_prefix]
+
+ # don't forget to account for the prefix value!
+ return (unit_data[0] * prefix_value, unit_data[1])
+
+ # no dice
+ raise UnitParseError("Could not find unit symbol '%s'. Please supply the dimensions and cgs value when creating this object." % symbol_str)
+
+
+def get_conversion_factor(old_units, new_units):
+ """
+ Get the conversion factor between two units of equivalent dimensions. This
+ is the number you multiply data by to convert from values in `old_units` to
+ values in `new_units`.
+
+ Parameters
+ ----------
+ old_units: str or Unit object
+ The current units.
+ new_units : str or Unit object
+ The units we want.
+
+ Returns
+ -------
+ conversion_factor : float
+ `old_units / new_units`
+
+ """
+ # Make units out of strings if we need to.
+ if isinstance(old_units, str):
+ old_units = Unit(old_units)
+ if isinstance(new_units, str):
+ new_units = Unit(new_units)
+
+ if not old_units.same_dimensions_as(new_units):
+ raise UnitOperationError("Cannot convert from %s to %s because the dimensions do not match: %s and %s" % (old_units, new_units, old_units.dimensions, new_units.dimensions))
+
+ return old_units.cgs_value / new_units.cgs_value
+
+
+def convert_values(values, old_units, new_units):
+ """
+ Take data given in old units and convert to values in new units.
+
+ Parameters
+ ----------
+ values : array_like
+ The number or array we will convert.
+ old_units : str or Unit object
+ The units values are supplied in.
+ new_units : str or Unit object
+ The units values will be returned in.
+
+ Returns values in new units.
+
+ """
+ return values * get_conversion_factor(old_units, new_units)
https://bitbucket.org/yt_analysis/yt-3.0/commits/50e6460dade1/
changeset: 50e6460dade1
branch: yt-3.0
user: MatthewTurk
date: 2013-03-06 22:35:59
summary: Merging Casey's changes
affected #: 2 files
diff -r 162feb61f27ac820077d7bc9bd81e92cb5a414c2 -r 50e6460dade1bc6cdd62012f7427f5c079f2fbe3 yt/utilities/tests/test_units.py
--- /dev/null
+++ b/yt/utilities/tests/test_units.py
@@ -0,0 +1,378 @@
+"""
+Test symbolic unit handling.
+
+Author: Casey W. Stark <caseywstark at gmail.com>
+Affiliation: UC Berkeley
+
+License:
+ Copyright (C) 2012 Casey W. Stark. All Rights Reserved.
+
+ This file is part of yt.
+
+ yt is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+
+import nose
+from numpy.testing import assert_approx_equal
+from sympy import Symbol
+
+# base dimensions
+from yt.utilities.units import mass, length, time, temperature
+# functions
+from yt.utilities.units import get_conversion_factor, make_symbols_positive
+# classes
+from yt.utilities.units import Unit, UnitParseError
+# objects
+from yt.utilities.units import unit_symbols_dict, unit_prefixes
+
+
+def test_no_conflicting_symbols():
+ """
+ Check unit symbol definitions for conflicts.
+
+ """
+ full_set = set(unit_symbols_dict.keys())
+
+ # go through all possible prefix combos
+ for symbol in unit_symbols_dict.keys():
+ for prefix in unit_prefixes.keys():
+ new_symbol = "%s%s" % (prefix, symbol)
+
+ # test if we have seen this symbol
+ if new_symbol in full_set:
+ print "Duplicate symbol: %s" % new_symbol
+ assert False
+
+ full_set.add(new_symbol)
+
+
+def test_dimensionless():
+ """
+ Create dimensionless unit and check attributes.
+
+ """
+ u1 = Unit()
+
+ assert u1.is_dimensionless
+ assert u1.expr == 1
+ assert u1.cgs_value == 1
+ assert u1.dimensions == 1
+
+#
+# Start init tests
+#
+
+def test_create_from_string():
+ """
+ Create units with strings and check attributes.
+
+ """
+ from yt.utilities.units import energy
+
+ u1 = Unit("g * cm**2 * s**-2")
+ assert u1.dimensions == energy
+ assert u1.cgs_value == 1.0
+
+ # make sure order doesn't matter
+ u2 = Unit("cm**2 * s**-2 * g")
+ assert u2.dimensions == energy
+ assert u2.cgs_value == 1.0
+
+
+def test_create_from_expr():
+ """
+ Create units from sympy Exprs and check attributes.
+
+ """
+ pc_cgs = 3.08568e18
+ yr_cgs = 31536000
+
+ # Symbol expr
+ s1 = Symbol("pc", positive=True)
+ s2 = Symbol("yr", positive=True)
+ # Mul expr
+ s3 = s1 * s2
+ # Pow expr
+ s4 = s1**2 * s2**(-1)
+
+ u1 = Unit(s1)
+ u2 = Unit(s2)
+ u3 = Unit(s3)
+ u4 = Unit(s4)
+
+ assert u1.expr == s1
+ assert u2.expr == s2
+ assert u3.expr == s3
+ assert u4.expr == s4
+
+ assert u1.cgs_value == pc_cgs
+ assert u2.cgs_value == yr_cgs
+ assert u3.cgs_value == pc_cgs * yr_cgs
+ assert u4.cgs_value == pc_cgs**2 / yr_cgs
+
+ assert u1.dimensions == length
+ assert u2.dimensions == time
+ assert u3.dimensions == length * time
+ assert u4.dimensions == length**2 / time
+
+
+def test_create_with_duplicate_dimensions():
+ """
+ Create units with overlapping dimensions. Ex: km/Mpc.
+
+ """
+ from yt.utilities.units import power, rate
+
+ u1 = Unit("erg * s**-1")
+ u2 = Unit("km/s/Mpc")
+ km_cgs = 1e5
+ Mpc_cgs = 3.08568e24
+
+ print u2.expr
+ print u2.dimensions
+ print u2.cgs_value
+
+ assert u1.cgs_value == 1
+ assert u1.dimensions == power
+
+ assert u2.cgs_value == km_cgs / Mpc_cgs
+ assert u2.dimensions == rate
+
+
+def test_create_new_symbol():
+ """
+ Create unit with unknown symbol.
+
+ """
+ u1 = Unit("abc", cgs_value=42, dimensions=(mass/time))
+
+ assert u1.expr == Symbol("abc", positive=True)
+ assert u1.cgs_value == 42
+ assert u1.dimensions == mass / time
+
+
+def test_create_fail_on_unknown_symbol():
+ """
+ Fail to create unit with unknown symbol, without cgs_value and dimensions.
+
+ """
+ try:
+ u1 = Unit(Symbol("jigawatts"))
+ except UnitParseError:
+ pass
+ else:
+ assert False
+
+
+def test_create_fail_on_bad_symbol_type():
+ """
+ Fail to create unit with bad symbol type.
+
+ """
+ try:
+ u1 = Unit([1]) # something other than Expr and str
+ except UnitParseError:
+ pass
+ else:
+ assert False
+
+
+def test_create_fail_on_bad_dimensions_type():
+ """
+ Fail to create unit with bad dimensions type.
+
+ """
+ try:
+ u1 = Unit("a", cgs_value=1, dimensions="(mass)")
+ except UnitParseError:
+ pass
+ else:
+ assert False
+
+
+def test_create_fail_on_dimensions_content():
+ """
+ Fail to create unit with bad dimensions expr.
+
+ """
+ a = Symbol("a")
+
+ try:
+ u1 = Unit("a", cgs_value=1, dimensions=a)
+ except UnitParseError:
+ pass
+ else:
+ assert False
+
+
+def test_create_fail_on_cgs_value_type():
+ """
+ Fail to create unit with bad cgs_value type.
+
+ """
+ try:
+ u1 = Unit("a", cgs_value="a", dimensions=(mass/time))
+ except UnitParseError:
+ pass
+ else:
+ assert False
+
+#
+# End init tests
+#
+
+def test_string_representation():
+ """
+ Check unit string representation.
+
+ """
+ pc = Unit("pc")
+ Myr = Unit("Myr")
+ speed = pc / Myr
+ dimensionless = Unit()
+
+ assert str(pc) == "pc"
+ assert str(Myr) == "Myr"
+ assert str(speed) == "pc/Myr"
+ assert repr(speed) == "pc/Myr"
+ assert str(dimensionless) == "(dimensionless)"
+
+#
+# Start operation tests
+#
+
+def test_multiplication():
+ """
+ Multiply two units.
+
+ """
+ msun_cgs = 1.98892e33
+ pc_cgs = 3.08568e18
+
+ # Create symbols
+ msun_sym = Symbol("Msun", positive=True)
+ pc_sym = Symbol("pc", positive=True)
+ s_sym = Symbol("s", positive=True)
+
+ # Create units
+ u1 = Unit("Msun")
+ u2 = Unit("pc")
+
+ # Mul operation
+ u3 = u1 * u2
+
+ assert u3.expr == msun_sym * pc_sym
+ assert u3.cgs_value == msun_cgs * pc_cgs
+ assert u3.dimensions == mass * length
+
+ # Pow and Mul operations
+ u4 = Unit("pc**2")
+ u5 = Unit("Msun * s")
+
+ u6 = u4 * u5
+
+ assert u6.expr == pc_sym**2 * msun_sym * s_sym
+ assert u6.cgs_value == pc_cgs**2 * msun_cgs
+ assert u6.dimensions == length**2 * mass * time
+
+
+def test_division():
+ """
+ Divide two units.
+
+ """
+ pc_cgs = 3.08568e18
+ km_cgs = 1e5
+
+ # Create symbols
+ pc_sym = Symbol("pc", positive=True)
+ km_sym = Symbol("km", positive=True)
+ s_sym = Symbol("s", positive=True)
+
+ # Create units
+ u1 = Unit("pc")
+ u2 = Unit("km * s")
+
+ u3 = u1 / u2
+
+ assert u3.expr == pc_sym / (km_sym * s_sym)
+ assert u3.cgs_value == pc_cgs / km_cgs
+ assert u3.dimensions == 1 / time
+
+
+def test_power():
+ """
+ Take units to some power.
+
+ """
+ from sympy import nsimplify
+
+ pc_cgs = 3.08568e18
+ mK_cgs = 1e-3
+ u1_dims = mass * length**2 * time**-3 * temperature**4
+ u1 = Unit("g * pc**2 * s**-3 * mK**4")
+
+ u2 = u1**2
+
+ assert u2.dimensions == u1_dims**2
+ assert u2.cgs_value == (pc_cgs**2 * mK_cgs**4)**2
+
+ u3 = u1**(-1.0/3)
+
+ assert u3.dimensions == nsimplify(u1_dims**(-1.0/3))
+ assert u3.cgs_value == (pc_cgs**2 * mK_cgs**4)**(-1.0/3)
+
+
+def test_equality():
+ """
+ Check unit equality with different symbols, but same dimensions and cgs_value.
+
+ """
+ u1 = Unit("km * s**-1")
+ u2 = Unit("m * ms**-1")
+
+ assert u1 == u2
+
+#
+# End operation tests.
+#
+
+def test_cgs_equivalent():
+ """
+ Check cgs equivalent of a unit.
+
+ """
+ Msun_cgs = 1.98892e33
+ Mpc_cgs = 3.08568e24
+
+ u1 = Unit("Msun * Mpc**-3")
+ u2 = Unit("g * cm**-3")
+ u3 = u1.get_cgs_equivalent()
+
+ assert u2.expr == u3.expr
+ assert u2 == u3
+
+ assert_approx_equal( u1.cgs_value, Msun_cgs / Mpc_cgs**3, significant=16)
+ assert u2.cgs_value == 1
+ assert u3.cgs_value == 1
+
+ mass_density = mass / length**3
+
+ assert u1.dimensions == mass_density
+ assert u2.dimensions == mass_density
+ assert u3.dimensions == mass_density
+
+ assert_approx_equal( get_conversion_factor(u1, u3), Msun_cgs / Mpc_cgs**3,
+ significant=16 )
diff -r 162feb61f27ac820077d7bc9bd81e92cb5a414c2 -r 50e6460dade1bc6cdd62012f7427f5c079f2fbe3 yt/utilities/units.py
--- /dev/null
+++ b/yt/utilities/units.py
@@ -0,0 +1,448 @@
+"""
+Symbolic unit handling.
+
+Author: Casey W. Stark <caseywstark at gmail.com>
+Affiliation: UC Berkeley
+
+License:
+ Copyright (C) 2012 Casey W. Stark. All Rights Reserved.
+
+ This file is part of yt.
+
+ yt is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+
+from sympy import Expr, Mul, Number, Pow, Rational, Symbol
+from sympy import nsimplify, posify, sympify
+from sympy.parsing.sympy_parser import parse_expr
+
+# The base dimensions
+mass = Symbol("(mass)", positive=True)
+length = Symbol("(length)", positive=True)
+time = Symbol("(time)", positive=True)
+temperature = Symbol("(temperature)", positive=True)
+base_dimensions = [mass, length, time, temperature]
+
+### Misc. dimensions
+rate = 1 / time
+
+velocity = length / time
+acceleration = length / time**2
+jerk = length / time**3
+snap = length / time**4
+crackle = length / time**5
+pop = length / time**6
+
+momentum = mass * velocity
+force = mass * acceleration
+energy = force * length
+power = energy / time
+charge = (energy * length)**Rational(1, 2) # proper 1/2 power
+
+electric_field = charge / length**2
+magnetic_field = electric_field
+
+# Dictionary that holds information of known unit symbols.
+# The key is the symbol, the value is a tuple with the conversion factor to cgs,
+# and the dimensionality.
+unit_symbols_dict = {
+ # base
+ "g": (1.0, mass),
+ #"cm": (1.0, length), # duplicate with meter below...
+ "s": (1.0, time),
+ "K": (1.0, temperature),
+
+ # other cgs
+ "dyne": (1.0, force),
+ "erg": (1.0, energy),
+ "esu": (1.0, charge),
+
+ # some SI
+ "m": (1.0e2, length),
+ "J": (1.0e7, energy),
+ "Hz": (1.0, rate),
+
+ # times
+ "min": (60.0, time),
+ "hr": (3600.0, time),
+ "day": (86400.0, time),
+ "yr": (31536000.0, time),
+
+ # Solar units
+ "Msun": (1.98892e33, mass),
+ "Rsun": (6.96e10, length),
+ "Lsun": (3.9e33, power),
+ "Tsun": (5870.0, temperature),
+
+ # astro distances
+ "AU": (1.49598e13, length),
+ "ly": (9.46053e17, length),
+ "pc": (3.08568e18, length),
+
+ # other astro
+ "H_0": (2.3e-18, rate), # check cf
+
+ # other energy units
+ "eV": (1.6021766e-12, energy),
+
+ # electric stuff
+ "gauss": (1.0, magnetic_field),
+}
+
+# This dictionary formatting from magnitude package, credit to Juan Reyero.
+unit_prefixes = {
+ 'Y': 1e24, # yotta
+ 'Z': 1e21, # zetta
+ 'E': 1e18, # exa
+ 'P': 1e15, # peta
+ 'T': 1e12, # tera
+ 'G': 1e9, # giga
+ 'M': 1e6, # mega
+ 'k': 1e3, # kilo
+ 'd': 1e1, # deci
+ 'c': 1e-2, # centi
+ 'm': 1e-3, # mili
+ 'u': 1e-6, # micro
+ 'n': 1e-9, # nano
+ 'p': 1e-12, # pico
+ 'f': 1e-15, # femto
+ 'a': 1e-18, # atto
+ 'z': 1e-21, # zepto
+ 'y': 1e-24, # yocto
+}
+
+
+class UnitParseError(Exception):
+ pass
+class UnitOperationError(Exception):
+ pass
+
+
+class Unit(Expr):
+ """
+ A symbolic unit, using sympy functionality. We only add "dimensions" so that
+ sympy understands relations between different units.
+
+ """
+
+ is_positive = True # make sqrt(m**2) --> m
+ is_commutative = True
+ is_number = False
+
+ __slots__ = ["expr", "cgs_value", "dimensions", "is_atomic"]
+
+ def __new__(cls, unit_expr=sympify(1), cgs_value=None, dimensions=None,
+ **assumptions):
+ """
+ Create a new unit. May be an atomic unit (like a gram) or a combination
+ of other units (like g / cm**3).
+
+ Parameters
+ ----------
+ unit_expr : string or sympy.core.expr.Expr
+ The unit symbol expression.
+ cgs_value : float
+ This unit's value in cgs.
+ dimensions : sympy.core.expr.Expr
+ An expression representing the dimensionality of this unit. This
+ should be a product (sympy.core.mul.Mul object) of mass, length,
+ time, and temperature objects to various powers.
+
+ """
+ # if we have a string, parse into an expression
+ if isinstance(unit_expr, str):
+ unit_expr = parse_expr(unit_expr)
+
+ if not isinstance(unit_expr, Expr):
+ raise UnitParseError("Unit representation must be a string or sympy Expr. %s is a %s." % (unit_expr, type(unit_expr)))
+ # done with argument checking...
+
+ # sympify, make positive symbols, and nsimplify the expr
+ unit_expr = sympify(unit_expr)
+ unit_expr = make_symbols_positive(unit_expr)
+ unit_expr = nsimplify(unit_expr)
+
+ # see if the unit is atomic.
+ is_atomic = False
+ if isinstance(unit_expr, Symbol):
+ is_atomic = True
+
+ # Did the user supply cgs_value and dimensions?
+ if cgs_value and not dimensions or dimensions and not cgs_value:
+ raise Exception("If you provide cgs_vale or dimensions, you must provide both. cgs_value is %s, dimensions is %s." % (cgs_value, dimensions))
+
+ if cgs_value and dimensions:
+ # check that cgs_value is a float or can be converted to one
+ try:
+ cgs_value = float(cgs_value)
+ except ValueError:
+ raise UnitParseError("Please provide a float for the cgs_value kwarg. I got '%s'." % cgs_value)
+ # check that dimensions is valid
+ dimensions = verify_dimensions(dimensions)
+ # save the values
+ this_cgs_value, this_dimensions = cgs_value, dimensions
+
+ else: # lookup the unit symbols
+ this_cgs_value, this_dimensions = \
+ get_unit_data_from_expr(unit_expr)
+
+ # Trick to get dimensions powers as Rationals
+ this_dimensions = nsimplify(this_dimensions)
+
+ # create obj with superclass construct
+ obj = Expr.__new__(cls, **assumptions)
+
+ # attach attributes to obj
+ obj.expr = unit_expr
+ obj.is_atomic = is_atomic
+ obj.cgs_value = this_cgs_value
+ obj.dimensions = this_dimensions
+
+ # return `obj` so __init__ can handle it.
+ return obj
+
+ ### some sympy conventions I guess
+ def __getnewargs__(self):
+ return (self.expr, self.is_atomic, self.cgs_value, self.dimensions)
+
+ def __hash__(self):
+ return super(Unit, self).__hash__()
+
+ def _hashable_content(self):
+ return (self.expr, self.is_atomic, self.cgs_value, self.dimensions)
+ ### end sympy conventions
+
+ def __repr__(self):
+ if self.expr == 1:
+ return "(dimensionless)"
+ return str(self.expr)
+
+ def __str__(self):
+ if self.expr == 1:
+ return "(dimensionless)"
+ return str(self.expr)
+
+ # for sympy.printing
+ def _sympystr(self, *args):
+ return str(self.expr)
+
+ ### override sympy operations
+ def __mul__(self, u):
+ """ Multiply Unit with u (Unit object). """
+ if not isinstance(u, Unit):
+ raise UnitOperationError("Tried to multiply Unit object with '%s'. This behavior is undefined." % u)
+
+ return Unit(self.expr * u.expr, self.cgs_value * u.cgs_value,
+ self.dimensions * u.dimensions)
+
+ def __div__(self, u):
+ """ Divide Unit by u (Unit object). """
+ if not isinstance(u, Unit):
+ raise UnitOperationError("Tried to divide Unit object by '%s'. This behavior is undefined." % u)
+
+ return Unit(self.expr / u.expr, self.cgs_value / u.cgs_value,
+ self.dimensions / u.dimensions)
+
+ def __pow__(self, p):
+ """ Take Unit to power p (float). """
+ try:
+ p = sympify(p)
+ except ValueError:
+ raise UnitOperationError("Tried to take Unit object to the power '%s'. I could not cast this to a float." % p)
+
+ return Unit(self.expr**p, self.cgs_value**p, self.dimensions**p)
+
+ ### Comparison operators
+ def same_dimensions_as(self, other_unit):
+ """ Test if dimensions are the same. """
+ return (self.dimensions / other_unit.dimensions) == 1
+
+ def __eq__(self, u):
+ """ Test unit equality. """
+ if not isinstance(u, Unit):
+ raise UnitOperationError("Tried to test equality between Unit object and '%s'. This behavior is undefined." % u)
+
+ return (self.cgs_value == u.cgs_value and self.dimensions == u.dimensions)
+
+ @property
+ def is_dimensionless(self):
+ return self.dimensions == 1
+
+ def get_cgs_equivalent(self):
+ """ Create and return dimensionally-equivalent cgs units. """
+ cgs_units_string = "g**(%s) * cm**(%s) * s**(%s) * K**(%s)" % \
+ (self.dimensions.expand().as_coeff_exponent(mass)[1],
+ self.dimensions.expand().as_coeff_exponent(length)[1],
+ self.dimensions.expand().as_coeff_exponent(time)[1],
+ self.dimensions.expand().as_coeff_exponent(temperature)[1])
+ return Unit(cgs_units_string, 1, self.dimensions)
+
+
+def make_symbols_positive(expr):
+ """
+ Grabs all symbols from expr, makes new positive symbols with the same names,
+ and substitutes them back into the expression.
+
+ """
+ expr_symbols = expr.atoms(Symbol) # grab all symbols
+
+ # Replace one at a time
+ for s in expr_symbols:
+ # replace this symbol with a positive version
+ expr = expr.subs(s, Symbol(s.name, positive=True))
+
+ return expr
+
+
+# @todo: simpler method that doesn't use recursion would be better...
+# We could check if dimensions.atoms are all numbers or symbols, but we should
+# check the functions also...
+def verify_dimensions(d):
+ """
+ Make sure that `d` is a valid dimension expression. It must consist of only
+ the base dimension symbols, to powers, multiplied together. If valid, return
+ the simplified expression. If not, raise an Exception.
+
+ """
+ # in the case of a Number of Symbol, we can just return
+ if isinstance(d, Number):
+ return d
+ elif isinstance(d, Symbol):
+ if d in base_dimensions:
+ return d
+ else:
+ raise UnitParseError("dimensionality expression contains an unknown symbol '%s'." % d)
+
+ # validate args of a Pow or Mul separately
+ elif isinstance(d, Pow):
+ return verify_dimensions(d.args[0])**verify_dimensions(d.args[1])
+
+ elif isinstance(d, Mul):
+ total_mul = 1
+ for arg in d.args:
+ total_mul *= verify_dimensions(arg)
+ return total_mul
+
+ # should never get here
+ raise UnitParseError("Bad dimensionality expression '%s'." % d)
+
+
+def get_unit_data_from_expr(unit_expr):
+ """
+ Gets total cgs_value and dimensions from a valid unit expression.
+
+ """
+ # The simplest case first
+ if isinstance(unit_expr, Unit):
+ return (unit_expr.cgs_value, unit_expr.dimensions)
+
+ # Now for the sympy possibilities
+ if isinstance(unit_expr, Symbol):
+ return lookup_unit_symbol(str(unit_expr))
+
+ if isinstance(unit_expr, Number):
+ return (1, 1)
+
+ if isinstance(unit_expr, Pow):
+ unit_data = get_unit_data_from_expr(unit_expr.args[0])
+ power = unit_expr.args[1]
+ return (unit_data[0]**power, unit_data[1]**power)
+
+ if isinstance(unit_expr, Mul):
+ cgs_value = 1
+ dimensions = 1
+ for expr in unit_expr.args:
+ unit_data = get_unit_data_from_expr(expr)
+ cgs_value *= unit_data[0]
+ dimensions *= unit_data[1]
+
+ return (cgs_value, dimensions)
+
+ raise UnitParseError("Cannot parse for unit data from '%s'. Please supply an expression of only Unit, Symbol, Pow, and Mul objects." % str(unit_expr))
+
+
+def lookup_unit_symbol(symbol_str):
+ """ Searches for the unit data typle corresponding to the given symbol. """
+
+ if symbol_str in unit_symbols_dict:
+ # lookup successful, return the tuple directly
+ return unit_symbols_dict[symbol_str]
+
+ # could still be a known symbol with a prefix
+ possible_prefix = symbol_str[0]
+ if possible_prefix in unit_prefixes:
+ # the first character could be a prefix, check the rest of the symbol
+ symbol_wo_prefix = symbol_str[1:]
+
+ if symbol_wo_prefix in unit_symbols_dict:
+ # lookup successful, it's a symbol with a prefix
+ unit_data = unit_symbols_dict[symbol_wo_prefix]
+ prefix_value = unit_prefixes[possible_prefix]
+
+ # don't forget to account for the prefix value!
+ return (unit_data[0] * prefix_value, unit_data[1])
+
+ # no dice
+ raise UnitParseError("Could not find unit symbol '%s'. Please supply the dimensions and cgs value when creating this object." % symbol_str)
+
+
+def get_conversion_factor(old_units, new_units):
+ """
+ Get the conversion factor between two units of equivalent dimensions. This
+ is the number you multiply data by to convert from values in `old_units` to
+ values in `new_units`.
+
+ Parameters
+ ----------
+ old_units: str or Unit object
+ The current units.
+ new_units : str or Unit object
+ The units we want.
+
+ Returns
+ -------
+ conversion_factor : float
+ `old_units / new_units`
+
+ """
+ # Make units out of strings if we need to.
+ if isinstance(old_units, str):
+ old_units = Unit(old_units)
+ if isinstance(new_units, str):
+ new_units = Unit(new_units)
+
+ if not old_units.same_dimensions_as(new_units):
+ raise UnitOperationError("Cannot convert from %s to %s because the dimensions do not match: %s and %s" % (old_units, new_units, old_units.dimensions, new_units.dimensions))
+
+ return old_units.cgs_value / new_units.cgs_value
+
+
+def convert_values(values, old_units, new_units):
+ """
+ Take data given in old units and convert to values in new units.
+
+ Parameters
+ ----------
+ values : array_like
+ The number or array we will convert.
+ old_units : str or Unit object
+ The units values are supplied in.
+ new_units : str or Unit object
+ The units values will be returned in.
+
+ Returns values in new units.
+
+ """
+ return values * get_conversion_factor(old_units, new_units)
Repository URL: https://bitbucket.org/yt_analysis/yt-3.0/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
More information about the yt-svn
mailing list