Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved sub-routine for string quantity arguments for find_unit #13875

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

Shikhar1998
Copy link
Contributor

Re-written sub-routine for string quantity arguments for find_unit.

References to other Issues or PRs

Fixes #13874

Brief description of what is fixed or changed

The input element is searched with all the elements of units dictionary and matched for theirdimensions.

Other comments

Moreover, in PR #13438 problem of multiple instances of plank_length and plank_charge in resultant list for find_unit('charge') and find_unit('length') was raised.

Example:

>>> u.find_unit('charge')
['C', 'coulomb', 'coulombs', 'planck_charge', 'planck_charge']

To counter this problem, set was used. However, there is no special need for it, as all the definitions are inherently unique. This problem has also been eliminated.

Re-written sub-routine for string quantity arguments for find_unit.
@Shikhar1998 Shikhar1998 changed the title Re-written sub-routine for string quantity arguments for find_unit. Improved sub-routine for string quantity arguments for find_unit Jan 9, 2018
Now covers all possible cases
Copy link
Member

@ylemkimon ylemkimon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@@ -222,23 +222,28 @@ def find_unit(quantity):

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think docstring should explain its exact behavior

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ylemkimon, I have made the requested changes, please have a look.

Copy link
Member

@ylemkimon ylemkimon Jan 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all instances of units/dimensions with these arguments. is little ambiguous.
If a string is given, it currently returns
(i) all units matching the string
(ii) if a dimension exactly matching the string exists, all units with that dimension
(iii) if a unit exactly matching the string exists, all units with same dimension

Explained the result for possible 2 input cases: 1. String and 2. Quantity/Dimension
72 character rule
@Shikhar1998
Copy link
Contributor Author

@smichr, can you please review this PR.

if isinstance(dim, Quantity): dim = dim.dimension
for i in dir(u):
quant_attr = getattr(u, i)
if quantity in i or (isinstance(quant_attr, Quantity) and quant_attr.dimension == dim):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it should return Dimensions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, as name of function find_unit itself implies to find all instances of that unit. However, original docstring states that function should return all Dimensions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A separate function like find_dimension can be added to return all the dimensions. Please suggest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, the results of these functions (find_unit and find_dimension) would be quite alike, which may make this exercise quite dispensable.

@Shikhar1998
Copy link
Contributor Author

Done!! ping @ylemkimon .

@Shikhar1998
Copy link
Contributor Author

Shikhar1998 commented Jan 17, 2018

@smichr, as you were associated with PR #13438 can you please give some comments on the current changes. Thanks!!

@Shikhar1998
Copy link
Contributor Author

@ylemkimon, ping.

@ylemkimon
Copy link
Member

ylemkimon commented Jan 20, 2018

I think documentation needs improvement, but I'm not a native English speaker, so I cannot think of better one.

@Shikhar1998
Copy link
Contributor Author

Shikhar1998 commented Jan 20, 2018

@asmeurer, do you have some suggestions to improve the documentation.

@@ -210,12 +210,18 @@

def find_unit(quantity):
"""
Return a list of matching units or dimension names.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it would be clearer to just demonstrate by example rather than to write an abstract statement which will be hard to understand unless you already understand it or until you see an example:

"""
Return the list of units that match the given quantity.

Parameters
==========

quantity : string, Dimension, or Quantity

Examples
========

>>> from sympy.physics import units as u

When the quantity is a unit given is a string or unit, ...

>>> u.find('charge')  # unit as string
>>> u.find(u.charge) # unit as unit

When the quantity given is a Dimension, ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite a nice idea, thanks for the insight. Made changes, please check.

Made changes as suggested by @smichr
>>> u.find_unit(u.inch**3)[:5]
['l', 'cl', 'dl', 'ml', 'liter']

# When the input is a string, it returns units which have input
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two comments:

  • go ahead and write the comments without the leading #
  • the last comment about using strings is a bit confusing, perhaps
    because of 'sub-script'. I think "substring" would be more recognized in this context. Myabe
When the input is a string, units or Quantities are returned. The units will
have the input as a substring; Quantities will a dimension that matches the
input.

But if that is right, I'm not sure why the behavior of trying to match dimensions was added. That can already be done with u.dimension. string input is used when you aren't sure of the spelling or quantity that you are looking for; once you find it you can then use the corresponding dimension in your next search. Is there an unecessary complication by combining both outputs for a string input? e.g. if you can't do [u.find_unit(gettattr(u, i)) for i in u.find_unit('foo')] ... or soemthing like that ... then this is maybe not a good direction to go.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That can already be done with u.dimension. string input is used when you aren't sure of the spelling or quantity that you are looking for; once you find it you can then use the corresponding dimension in your next search.

Hmm.., I have a simple idea for this. We can match the various quantities in the dictionary u for given string and give outputs like this:

 >>> u.find_unit('char')
do you mean: charge                         # for incomplete entries
 >>> u.find_unit('charge')
1                                               # a perfect match
 >>> u.find_unit('0x00x0')
0                                               # no match
OR
simply an array of suitable matches ['charge']

@smichr
Copy link
Member

smichr commented Jan 26, 2018

What do you think about these changes:

diff --git a/sympy/physics/units/__init__.py b/sympy/physics/units/__init__.py
index 75de4ee..dc38476 100644
--- a/sympy/physics/units/__init__.py
+++ b/sympy/physics/units/__init__.py
@@ -249,29 +249,38 @@ def find_unit(quantity):
 
     """
     import sympy.physics.units as u
-    rv = []
+    rv = set()
+    IA = [(i, getattr(u, i)) for i in dir(u)]
+    IA = [(i, a) for i, a in IA if isinstance(a, (Quantity, Dimension))]
     if isinstance(quantity, string_types):
-        dim = getattr(u, quantity)
-        if isinstance(dim, Quantity): dim = dim.dimension
-        for i in dir(u):
-            quant_attr = getattr(u, i)
-            if quantity in i or (isinstance(quant_attr, Quantity) and quant_attr.dimension == dim):
-                if not isinstance(quant_attr, Dimension): rv.append(i)
-
+        dim = getattr(u, quantity, None)
+        if dim is None:
+            for i, a in IA:
+                if quantity in i:
+                    rv.add(i)
+                    rv.update(find_unit(i))
+        else:
+            rv.update([i for i, _ in IA if quantity in i])
+            if isinstance(dim, Quantity):
+                dim = dim.dimension
+            for i, quant_attr in IA:
+                if quantity in i or (isinstance(quant_attr, Quantity)
+                        and quant_attr.dimension == dim):
+                    if not isinstance(quant_attr, Dimension):
+                        rv.add(i)
     else:
-        for i in sorted(dir(u)):
-            other = getattr(u, i)
-            if not isinstance(other, Quantity):
-                continue
+        for i, other in IA:
             if isinstance(quantity, Quantity):
-                if quantity.dimension == other.dimension:
-                    rv.append(str(i))
+                if isinstance(other, Quantity) and quantity.dimension == other.dimension:
+                    rv.add(i)
             elif isinstance(quantity, Dimension):
-                if other.dimension == quantity:
-                    rv.append(str(i))
-            elif other.dimension == Dimension(Quantity.get_dimensional_expr(quantity)):
-                rv.append(str(i))
-    return sorted(rv, key=len)
+                if isinstance(other, Quantity) and other.dimension == quantity:
+                    rv.add(i)
+            elif isinstance(other, Quantity) and other.dimension == Dimension(Quantity.get_dimensional_expr(quantity)):
+                rv.add(i)
+    if not rv:
+        return []
+    return list(list(zip(*sorted(zip([len(i) for i in rv], rv))))[1])
 
 # NOTE: the old units module had additional variables:
 # 'density', 'illuminance', 'resistance'.
diff --git a/sympy/physics/units/tests/test_quantities.py b/sympy/physics/units/tests/test_quantities.py
index fd9f6b0..94bda98 100644
--- a/sympy/physics/units/tests/test_quantities.py
+++ b/sympy/physics/units/tests/test_quantities.py
@@ -221,6 +221,8 @@ def test_find_unit():
     assert find_unit('coulomb') == ['C', 'coulomb', 'coulombs',
                                     'planck_charge', 'coulomb_constant']
     assert find_unit(coulomb) == ['C', 'coulomb', 'coulombs', 'planck_charge']
+    assert find_unit('char') == ['C', 'charge', 'coulomb', 'coulombs', 'planck_charge']
+    assert find_unit('charge') == ['C', 'charge', 'coulomb', 'coulombs', 'planck_charge']
     assert find_unit(charge) == ['C', 'coulomb', 'coulombs', 'planck_charge']
     assert find_unit(inch) == [
         'm', 'au', 'cm', 'dm', 'ft', 'km', 'ly', 'mi', 'mm', 'nm', 'pm', 'um',
@@ -237,7 +239,7 @@ def test_find_unit():
         'l', 'cl', 'dl', 'ml', 'liter', 'quart', 'liters', 'quarts',
         'deciliter', 'centiliter', 'deciliters', 'milliliter',
         'centiliters', 'milliliters', 'planck_volume']
-    assert find_unit('voltage') == ['V', 'v', 'volt', 'volts', 'planck_voltage']
+    assert find_unit('voltage') == ['V', 'v', 'volt', 'volts', 'voltage', 'planck_voltage']
 
 
 def test_Quantity_derivative():

Changes made as suggested by @smichr
@Shikhar1998
Copy link
Contributor Author

@smichr, Thank's for the valuable input. I have committed these suggested changes. I had one question regarding the current implementation; when u.find_unit('') is executed, it returns the entire units dictionary. Is this a valid behavior or not?

@ylemkimon
Copy link
Member

@smichr

IA = [(i, a) for i, a in IA if isinstance(a, (Quantity, Dimension))]

I still think Dimensions should not be included in the return value of find_unit.

@smichr
Copy link
Member

smichr commented Jan 27, 2018

I still think Dimensions should not be included in the return value of find_unit.

Can you give an example of what the output is that you would like it not to be?

@smichr
Copy link
Member

smichr commented Jan 28, 2018

Is this a valid behavior or not?

probably not...we could just special case that and return [].

@smichr
Copy link
Member

smichr commented Jan 29, 2018

@ylemkimon , why don't you think Dimensions should be included in the return result?

As it is now, the original intent of the docstring should be restored to indicate that dimensions may be returned, too. Otherwise, if there is a good reason not to, then only the code needs to be modified and the docstring left alone.

@smichr
Copy link
Member

smichr commented Jan 31, 2018

Would it make sense to return dimensions if the quantity is ''?

--- a/sympy/physics/units/__init__.py
+++ b/sympy/physics/units/__init__.py
@@ -249,34 +249,27 @@ def find_unit(quantity):

     """
     import sympy.physics.units as u
+    IQ = [(i, getattr(u, i)) for i in dir(u)]
+    if not quantity:
+        return sorted([(i, q) for i, q in IQ if isinstance(q, Dimension)])
+    IQ = [(i, q) for i, q in IQ if isinstance(q, Quantity)]
     rv = set()
-    IA = [(i, getattr(u, i)) for i in dir(u)]
-    IA = [(i, a) for i, a in IA if isinstance(a, (Quantity, Dimension))]
     if isinstance(quantity, string_types):
+        rv.update([i for i, _ in IQ if quantity in i])
         dim = getattr(u, quantity, None)
-        if dim is None:
-            for i, a in IA:
-                if quantity in i:
-                    rv.add(i)
-                    rv.update(find_unit(i))
-        else:
-            rv.update([i for i, _ in IA if quantity in i])
+        if dim is not None:
             if isinstance(dim, Quantity):
                 dim = dim.dimension
-            for i, quant_attr in IA:
-                if quantity in i or (isinstance(quant_attr, Quantity)
-                        and quant_attr.dimension == dim):
-                    if not isinstance(quant_attr, Dimension):
-                        rv.add(i)
-    else:
-        for i, other in IA:
-            if isinstance(quantity, Quantity):
-                if isinstance(other, Quantity) and quantity.dimension == other.dimension:
+            for i, quant in IQ:
+                if dim == quant.dimension:
                     rv.add(i)
-            elif isinstance(quantity, Dimension):
-                if isinstance(other, Quantity) and other.dimension == quantity:
-                    rv.add(i)
-            elif isinstance(other, Quantity) and other.dimension == Dimension(Quantity.get_dimensional_expr(quantity)):
+    else:
+        if isinstance(quantity, Quantity):
+            dim = quantity.dimension
+        else:
+            dim = Dimension(Quantity.get_dimensional_expr(quantity))
+        for i, quant in IQ:
+            if dim == quant.dimension:
                 rv.add(i)
     if not rv:
         return []

@ylemkimon
Copy link
Member

@smichr Dimension is a dimension of units, rather than a unit, and different class to Quantity. The user would expect to perform Quantity methods on units returned by find_unit.

@smichr
Copy link
Member

smichr commented Feb 1, 2018

@ylemkimon , here's an example of how someone might use the null find to get the dimensions and then use them for a further search:

>>> u.find_unit('')
['acceleration', 'action', 'amount', 'amount_of_substance', 'capacitance', 'char
ge', 'conductance', 'current', 'energy', 'force', 'frequency', 'impedance', 'ind
uctance', 'length', 'luminosity', 'luminous_intensity', 'magnetic_density', 'mag
netic_flux', 'magnetic_flux_density', 'mass', 'momentum', 'power', 'pressure', '
speed', 'temperature', 'time', 'velocity', 'voltage', 'volume']

>>> u.find_unit(u.length/u.time)
['sr', 'deg', 'mil', 'rad', 'degree', 'radian', 'degrees', 'percent', 'radians',
 'percents', 'permille', 'steradian', 'steradians', 'angular_mil', 'angular_mils
', 'avogadro_number']

...not sure why avogadro_number is showing up, though.

@Shikhar1998
Copy link
Contributor Author

@ylemkimon, will it be fine to rename the function as find_unit_dimension(). I think this will the eliminate the possibility of any unnecessary confusion in future.

@smichr
Copy link
Member

smichr commented Feb 1, 2018

rename the function as find_unit_dimension

Or a function named dimensions that simply returns the list sorted list of dimensions.

@Abdullahjavednesar Abdullahjavednesar added the PR: author's turn The PR has been reviewed and the author needs to submit more changes. label Apr 4, 2018
@oscarbenjamin oscarbenjamin added the physics.units Units and dimensions label Jan 24, 2019
@czgdp1807
Copy link
Member

@Upabjojr Is this PR having a chance to be merged?

@czgdp1807
Copy link
Member

The PR seems to be a valid one. @Shikhar1998 Would you still like to continue your work on this? Thanks for your contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Merge conflict physics.units Units and dimensions Please take over PR: author's turn The PR has been reviewed and the author needs to submit more changes.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Discrepancies in find_unit
6 participants