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

Defining your own dimensionless units #185

Closed
truemerrill opened this issue Aug 28, 2014 · 10 comments
Closed

Defining your own dimensionless units #185

truemerrill opened this issue Aug 28, 2014 · 10 comments

Comments

@truemerrill
Copy link

First off, I am a big fan of Pint and am using it in several of my projects. I have a question about defining custom dimensionless units. As an example of what I am trying to do, consider how Pint behaves with 'pi':

import pint

Registry = pint.UnitRegistry()
Quantity = Registry.Quantity
print(Quantity(1, 'pi').to('dimensionless'))

This code snippet prints 3.14159265359 dimensionless. Now suppose I want to define a new constant representing the electron g-factor (or pick any other dimensionless physical constant). I would suppose such a unit could be defined as follows:

import pint

Registry = pint.UnitRegistry()
Registry.define('electron_g_factor = -2.00231930436 * dimensionless = gs')
Quantity = Registry.Quantity

# Create a quantity with units of g_factors
g = Quantity(1, 'gs')
print(g)

# Convert g to dimenionless, similar to 'pi' (RAISES KeyError)
print(g.to('dimensionless'))

The first print statement prints 1 electron_g_factor. I would expect the second statement to print -2.00231930436 dimensionless but instead it raises a KeyError. The second statement fails because Pint can't convert to a dimensionless quantity. What gives? How can I define a dimensionless unit that behaves like 'pi'?

@truemerrill
Copy link
Author

I am using Pint 0.4.2 if that helps any.

@hgrecco
Copy link
Owner

hgrecco commented Aug 28, 2014

Thanks for the comments and the support. It is great to see that people are using Pint.

The plan is to make it intuitive (as all other things in Pint!) so that you can do:

Registry.define('electron_g_factor = -2.00231930436 = gs')

(Notice that dimensionless is not in the definition, but it should not matter if it is there).

However, this does not work in 0.5 (the current version) nor in the older ones.

This is something that I hope to get fixed by 0.6, but we are still brainstorming how to improve the way constants (with and without units) are handled. See #159, #17

Right now, the only way to achieve this is using some of the private API (I have tested against 0.5, but it should work against 0.4.2)

from pint.unit import UnitDefinition, ScaleConverter
Registry.define(UnitDefinition('electron_g_factor', 'gs', (), ScaleConverter(-2.00231930436)))

@truemerrill
Copy link
Author

Yep, I can confirm your method works in 0.4.2. Thanks for developing this extremely useful module, I can't tell you how many errors I have caught since using Pint in my physics codes.

@hgrecco
Copy link
Owner

hgrecco commented Aug 29, 2014

Thanks again! I am happy that you find it useful and feel free to provide feedback in terms of features that could be added. I will try to provide a better way to add constants in 0.6, but the way I have showed you will continue to work.

@hgrecco
Copy link
Owner

hgrecco commented Dec 22, 2015

I am closing this now. Feel free to reopen.

@hgrecco hgrecco closed this as completed Dec 22, 2015
@lafrech
Copy link

lafrech commented Apr 18, 2018

I'm discovering pint and I came across this while trying to define percent and permille units.

The import paths have changed since @hgrecco's comment above. Here's an updated version.

from pint.definitions import UnitDefinition
from pint.converters import ScaleConverter

ureg.define(UnitDefinition('percent', None, (), ScaleConverter(1/100)))
ureg.define(UnitDefinition('permille', None, (), ScaleConverter(1/1000)))

val = 12 * ureg.percent
print(val.to('permille'))
# 120.0 permille
print(val.to('dimensionless'))
# 0.12 dimensionless

Unfortunately, we still can't use the string form definition:

ureg.define('percent = 1/100')
ureg.define('permille = 1/1000')

val = 12 * ureg.percent
print(val.to('permille'))
# 12.0 permille
print(val.to('dimensionless'))
# 12.0 dimensionless

From what I found, this is because in the latter case, the unit is defined with is_base = True. It happens here because converter.keys() is empty.

I found out afterwards from #379 that I could use

ureg.define('percent = 0.01*count)
ureg.define('permille = 0.001*count)

but I figured I'd post this anway, at least for the import path update.

@lukelbd
Copy link
Contributor

lukelbd commented Apr 22, 2020

Thanks for the examples @lafrech.

I used your first method instead of #379 because then to_base_units() returns dimensionless units rather than count units, which conceptually makes more sense to me.

I also added "pre-processors" to the unit registry so that the symbols % and %% can be used as the canonical short forms for the percent and permille units (idea is taken from #429 but updated for the latest API). See the following code:

import pint
ureg = pint.UnitRegistry(preprocessors=[
    lambda s: s.replace('%%', ' permille '),
    lambda s: s.replace('%', ' percent '),
])
ureg.define(pint.unit.UnitDefinition(
    'permille', '%%', (), pint.converters.ScaleConverter(0.001),
))
ureg.define(pint.unit.UnitDefinition(
    'percent', '%', (), pint.converters.ScaleConverter(0.01),
))

This results in the following behavior:

>>> ureg('1%').to_base_units()
0.01 <Unit('dimensionless')>
>>> format(ureg('1%'), '~')
'1 %'
>>> format(ureg('1%%'), '~')
'1 %%'

@lukelbd
Copy link
Contributor

lukelbd commented Mar 5, 2021

Looks like the issue @lafrech mentioned has been fixed by version 0.16. The following now works:

import pint
ureg = pint.UnitRegistry(preprocessors=[
    lambda s: s.replace('%%', ' permille '),
    lambda s: s.replace('%', ' percent '),
])
ureg.define('permille = 0.001 = %%')  # or 'permille = 1 / 1000 = %%'
ureg.define('percent = 0.01 = %')

Results in:

>>> ureg('1 permille').to('')
0.001 <Unit('dimensionless')>

@mkaut
Copy link

mkaut commented Oct 2, 2022

With solution by @lukelbd,

x = ureg('1 percent').to('')

works and returns dimensionless 0.01, but

x.to('%')

does not - any way to get around this?

@ayding11
Copy link

ayding11 commented Nov 4, 2023

I have the same issue as @mkaut. With the solution by @lukelbd, converting from "dimensionless" or "percent" to "%" does not work. I am using Pint 0.22. Any workaround? Thanks.

ureg.Quantity(5, 'dimensionless').to('%')

produces:
File "/usr/local/lib/python3.10/dist-packages/pint/facets/plain/quantity.py", line 515, in to other = to_units_container(other, self._REGISTRY) File "/usr/local/lib/python3.10/dist-packages/pint/util.py", line 1041, in to_units_container return registry._parse_units(unit_like) File "/usr/local/lib/python3.10/dist-packages/pint/facets/nonmultiplicative/registry.py", line 70, in _parse_units return super()._parse_units(input_string, as_delta, case_sensitive) File "/usr/local/lib/python3.10/dist-packages/pint/facets/plain/registry.py", line 1153, in _parse_units units = ParserHelper.from_string(input_string, self.non_int_type) File "/usr/local/lib/python3.10/dist-packages/pint/util.py", line 764, in from_string ret = build_eval_tree(gen).evaluate( File "/usr/local/lib/python3.10/dist-packages/pint/pint_eval.py", line 329, in build_eval_tree result, _ = _build_eval_tree(tokens, op_priority, 0, 0) File "/usr/local/lib/python3.10/dist-packages/pint/pint_eval.py", line 254, in _build_eval_tree right, index = _build_eval_tree( File "/usr/local/lib/python3.10/dist-packages/pint/pint_eval.py", line 278, in _build_eval_tree assert result is not None AssertionError

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants