Context for volt/gauss -> volt/tesla
Hi everyone,
I am trying to write a transformation for V/G -> V/T and I don't get it done.
Just to double, check I tried the following code and it is running: pint_conversion1.py:
import pint
ureg = pint.UnitRegistry(system='SI')
ureg.load_definitions('pint_definition1.py')
ureg.enable_contexts('Gaussian')
ureg.enable_contexts('hall')
print(ureg.parse_expression('1nm').to('K', 'sp', 'boltzmann'))
print(ureg.parse_expression('1nm').to('Hz'))
print(ureg.parse_expression('VA').to('W'))
print(ureg.parse_expression('VA s').to('J'))
print(ureg.parse_expression('1J/m').to('VA s/m'))
print(ureg.parse_expression('1G').to('T'))
pint_definition1.py:
@context hall
[length] <-> [frequency]: speed_of_light / value
@end
So, the context seems to work in general.
Afterwards I used the following piece of code to get the transformation for my actual problem: pint_conversion2.py:
import pint
ureg = pint.UnitRegistry(system='SI')
ureg.load_definitions('pint_definition2.py')
ureg.enable_contexts('Gaussian')
ureg.enable_contexts('hall')
print(ureg.parse_expression('1G').to('T'))
print(ureg.parse_expression('V/G').to('V/T'))
pint_definition2.py:
@context hall
[electric_potential]/[gaussian_magnetic_flux] -> [electric_potential]/[magnetic_flux]: value * (4 * pi / vacuum_permeability) ** 0.5
[electric_potential]/[magnetic_flux] -> [electric_potential]/[gaussian_magnetic_flux]: value / (4 * pi / vacuum_permeability) ** 0.5
@end
But with this code I am getting the following error:
pint.errors.DimensionalityError: Cannot convert from 'gaussian_hall_sensitivity_absolute' ([length] ** 2.5 * [mass] ** 0.5 / [current] / [time] ** 2) to 'hall_sensitivity_absolute' ([length] ** 2 / [time])
I also tried the following code snippets: pint_conversion3.py:
import pint
ureg = pint.UnitRegistry(system='SI')
ureg.load_definitions('pint_definition3.py')
ureg.enable_contexts('Gaussian')
ureg.enable_contexts('hall')
print(ureg.parse_expression('1G').to('T'))
print(ureg.parse_expression('V/G').to('V/T'))
pint_definition3.py:
@context hall
[electric_potential]/[gaussian_magnetic_flux] -> [electric_potential]/[magnetic_flux]: value * (1 / 10**-7 / (V*s/A/m)) ** 0.5
[electric_potential]/[magnetic_flux] -> [electric_potential]/[gaussian_magnetic_flux]: value / (1 / 10**-7 / (V*s/A/m)) ** 0.5
@end
and: pint_conversion4.py:
import pint
ureg = pint.UnitRegistry(system='SI')
ureg.load_definitions('pint_definition4.py')
ureg.enable_contexts('Gaussian')
ureg.enable_contexts('hall')
print(ureg.parse_expression('1G').to('T'))
print(ureg.parse_expression('V/G').to('V/T'))
pint_definition4.py:
[gaussian_hall_sensitivity_absolute] = [electric_potential] / [gaussian_magnetic_flux]
[hall_sensitivity_absolute] = [electric_potential] / [magnetic_flux]
hall_sensitivity_absolute = V/T
gaussian_hall_sensitivity_absolute = V/G
@context hall
[gaussian_hall_sensitivity_absolute] -> [hall_sensitivity_absolute]: value * (4 * pi / vacuum_permeability) ** 0.5
[hall_sensitivity_absolute] -> [gaussian_hall_sensitivity_absolute]: value / (4 * pi / vacuum_permeability) ** 0.5
@end
but the error remains.
Additionally I was triing to create the context programmaticaly:
pint_conversion5.py:
import pint
ureg = pint.UnitRegistry(system='SI')
ureg.enable_contexts('Gaussian')
hall_def = pint.Context('hall')
hall_def.add_transformation('[electric_potential]/[gaussian_magnetic_flux]', '[electric_potential]/[magnetic_flux]', lambda ureg, x: x * (4 * pi / vacuum_permeability) ** 0.5)
ureg.add_context(hall_def)
print(ureg.parse_expression('V/G').to('V/T','hall'))
Still the same error.
Further I tried to convert another (meaningless) unit which is SI only, which worked just fine: pint_conversion6.py:
import pint
ureg = pint.UnitRegistry(system='SI')
ureg.load_definitions('pint_definition6.py')
print(ureg.parse_expression('V/nm').to('V Hz', 'hall'))
pint_definition6.py:
@context hall
[electric_potential]/[length] <-> [electric_potential]*[frequency]: value * speed_of_light
@end
V/G means Si and CGS systems are mixed. Might it be, that it is related to that? I think I have any error in reasoning and looked to long onto the same piece of code and therefore being blind for it. Any help is highly appreciated!
The example code is uploaded: code.zip
Thank you very much!
There is somethign I do not get from your snippet. In the second example you say you are getting: "gaussian_hall_sensitivity_absolute" but such dimensionality is no defined. Is that possible that there is something missing.
but in any case, mixing SI and CGS-Gaussian units is not a good idea as they are incompatible.
PS.- Do not name your definitions files .py, as they are not really python files and might lead to problems in the future. Use .txt
Thanks for your fast reply!
You are right, there was a copy paste error in the second example. I updated it. In the uploaded code.zip it should be right.
Ah, I wanted to mention that: I just use the py extension as I get some syntaxhighlighting, which is a little easier to read. I had txt before, this does not solve the problem.
For sure they are incompatible, therefore I would like to convert them. Is it possible to solve that in any way?
I mean this is the same equation as in default_en.txt in context gaussian [gaussian_magnetic_flux] -> [magnetic_flux]: value / (4 * π / µ_0) ** 0.5, why does it not work in my examples?
As I got that error I made a dimensional analysis by hand to check back if everything works out and it should result in the target dimension.
I've got the same issue trying to convert Oe to A/m:
[si_magnetic_field_strength] = [current] / [length]
[cgs_magnetic_field_strength] = [mass] ** 0.5 / [length] ** 0.5 / [time]
@context test
[cgs_magnetic_field_strength] <-> [si_magnetic_field_strength]: value * 1000 / (4 * π)
@end
from os import path
import pint
ureg = pint.UnitRegistry()
directory = path.dirname(path.abspath(__file__))
ureg.load_definitions(path.join(directory, 'unit_registry.txt'))
ureg.enable_contexts('test')
Unit = pint.Unit
Quantity = pint.Quantity
print(Quantity(1.0, ureg.oersted).to(ureg.ampere / ureg.meter))
# pint.errors.DimensionalityError: Cannot convert from 'oersted' ([mass] ** 0.5 / [length] ** 0.5 / [time]) to 'ampere / meter' ([current] / [length])
It seems that contexts aren't taken into account for mixed units. Here's an even simpler example using the built-in definitions and contexts:
import pint
ureg = pint.UnitRegistry()
(1 * ureg.eV).to(ureg.K, "boltzmann") # Works as expected
(1 * ureg.eV / ureg.m).to(ureg.K / ureg.m, "boltzmann") # Raises exception
# DimensionalityError: Cannot convert from 'electron_volt / meter' ([length] * [mass] / [time] ** 2) to 'kelvin / meter' ([temperature] / [length])
I guess this is because contexts look for the exact dimensional transformation, and combining with another dimension ruins it.
I have a bit of a brute-force method for this, that I was able to implement using the new facets system:
def _try_transform(self, src_value, src_unit, src_dim, dst_dim):
path = pint.util.find_shortest_path(self._active_ctx.graph, src_dim, dst_dim)
if not path:
return None
src = self.Quantity(src_value, src_unit)
for a, b in zip(path[:-1], path[1:]):
src = self._active_ctx.transform(a, b, self, src)
return src._magnitude, src._units
def _convert(self, value, src, dst, inplace=False):
"""Convert value from some source to destination units.
In addition to what is done by the PlainRegistry,
converts between units with different dimensions by following
transformation rules defined in the context.
Parameters
----------
value :
value
src : UnitsContainer
source units.
dst : UnitsContainer
destination units.
inplace :
(Default value = False)
Returns
-------
callable
converted value
"""
if not self._active_ctx:
return super()._convert(value, src, dst, inplace)
src_dim = self._get_dimensionality(src)
dst_dim = self._get_dimensionality(dst)
# Try converting the quantity with units as given
if converted := self._try_transform(value, src, src_dim, dst_dim):
value, src = converted
return super()._convert(value, src, dst, inplace)
# That wasn't possible, so now we break up the units and see
# if we can convert them individually.
# These are the new units resulting from any transformations
new_units = src
for unit, power in src.items():
# Here, we're assuming that the transformation is based on [dim]**1,
# while the unit in our quantity might be e.g. its inverse
unit_uc = pint.util.UnitsContainer({unit: 1})
unit_dim = self._get_dimensionality(unit_uc)
# Now we try to convert between this unit and one of the
# destination units
for dst_part, dst_power in dst.items():
dst_part_uc = pint.util.UnitsContainer({dst_part: 1})
dst_part_dim = self._get_dimensionality(dst_part_uc)
# If we're dealing with an inverse unit, we need to
# invert the value to get the transformation right.
# This is a bit hacky. Assuming we don't have any
# non-multiplicative units, we should always be able
# to convert zero though
try:
value_power = value**power
except ZeroDivisionError:
value_power = value
if converted := self._try_transform(
value_power, unit_uc, unit_dim, dst_part_dim
):
value, new_unit = converted
# Undo any inversions
try:
value = value**dst_power
except ZeroDivisionError:
value = value
# It worked, so we can replace the original unit
# with the transformed one
new_units = (
new_units
/ pint.util.UnitsContainer({unit: power})
* (new_unit**dst_power)
)
return super()._convert(value, new_units, dst, inplace)
It basically splits up the units in the source and destination, and tries to convert between them pair-wise.
This has meant we no longer need to define both the base transformation, and any transformations with additional units that we want to use.
This is obviously explodes combinatorially with the number units in source and destination, so maybe there's something cleverer that can be done.
How about trying to find the dimensions which are present in src but not in dst and viceversa and see if that conversion is present in the context?
Maybe? Looking at an example:
>>> src = 1 * ureg.kelvin / ureg.metre
>>> dst = ureg.eV / ureg.m
>>> src.dimensionality
# <UnitsContainer({'[length]': -1, '[temperature]': 1})>
>>> dst.dimensionality
# <UnitsContainer({'[length]': 1, '[mass]': 1, '[time]': -2})>
We'd be able to see [temperature] isn't in dst and then subtract the remaining units from dst, which should leave us with a valid conversion.
That wouldn't work if there were two separate conversions needed though.
I am closing this for the time being. feel free to reopen.