Pint revisited

Hi, I’ve been ramping up with param and really enjoying it so far. I am building some physical models that would benefit from unit support/ dimensional analysis. (Similar to Hints on how to extend to a new Parameter Type? - #3 by zworkinspace, not exactly like Advice on using Panel/Params with the Python Pint library (for physical quantities) which produces a widget for a standard pint Quantity.)

I have tried following the param.DataFrame source code to get started. Admittedly, I am a bit above my head with this and not exactly sure how the super methods are being used in the init and validation functions.

When I define the class as-is it will check for unit conflict on parameter initialization but not on setting the value. If I uncomment the line self._validate_units(val, self.default_units) I get an an AttributeError: 'Quantity' object has no attribute 'default_units'. Looking at both Number and DataFrame, this seems to be the basic procedure for checking for conflicts on initialization as well as value setting… but obviously I’m missing something. If anyone has suggestions I’d greatly appreciate it, and see how far along I can get this example!

from pint import UnitRegistry
from pint import Quantity as pQuantity
from param import ClassSelector

u = UnitRegistry()

class Quantity(ClassSelector):
    """
    Parameter whose value is a pint.Quantity.
    """
    __slots__ = ['default_units']
    def __init__(self, default=None, default_units=None, **params):#, unit=None, magnitude=None):
        super(Quantity,self).__init__(pQuantity, default=default, **params)

        if default and default_units:
            try:
                self.default = default.to(default_units)
            except:
                raise ValueError("Default value %s cannot be converted to default units %s" % (default, default_units))

        if default_units is not None:
            self.default_units = default_units

        self._validate(self.default)
        # self._validate(default)

    def _validate_units(self, val, default_units):
        if self.default_units is not None:
            try:
                val.to(default_units)
            except:
                raise ValueError("The input value %s has units that cannot be converted default units %s" % (val, default_units))

    def _validate(self, val):
        # super(Quantity, self)._validate(val) # is this needed?
        if self.allow_None and val is None:
            return

        # self._validate_units(val, self.default_units)
        
        pass

    @classmethod
    def serialize(cls, value):
        if value is None:
            return 'null'
        return str(value)

    @classmethod
    def deserialize(cls, value):
        if value == 'null':
            return None
        return pQuantity(value)

class TestClass(param.Parameterized):
    # quantity = Quantity(default=1*u.meter, default_units="watt")# fails, nice!
    quantity = Quantity(default=1*u.meter, default_units="kilometer")# works, nice!

#test = TestClass(quantity=1*u.watt) # Needs to throw error due to unit conflict...
test = TestClass(quantity=10*u.meter) # Works
1 Like

Well… after one more stare across the two reference codes I was able to get past this moving the super() under init to just before self._validate().

I will ponder what exactly is happening here and still open to advice for further implementation! :sweat_smile: (Next up would be bounds checking, and eventually a widget definition would be great.)

3 Likes