Hints on how to extend to a new Parameter Type?

First, thank you for a really cool package (indeed, thanks for all the marvelous toys in HoloViz)! I’ve created poor versions of Param in various languages over the years and was delighted to find you’ve put great thought into solving the problem really well for Python!

I would like to extend the code to be able to use numeric, units dimensioned, ‘quantity’ objects from the excellent astropy.units package as a parameter type. That way the parameters can be verified to be of the correct units, array dimensions as well as the values being (optionally) checked vs bounds or other criteria (e.g. mathematical properties of the input array).

I’m reading over the code and it seems I should follow (pillage!) the structure of the Number(Parameter) class. With a ‘quantity’, all the same behavior as Number applies to the quantity.value, but I need to add handling for the quantity.units. I will need to extend the slots for the units information.

Since I’m a newbie to this package - any other things I should be aware of?

(While this is brilliant for Python, it would all be way more generic to implement in Julia!!)

cheers!

1 Like

Hi @zworkinspace

Welcome :+1:

This might be a question @ceball og @jbednar can give a good answer to.

If you have not seen it, new and improved docs for param is coming. You can sneak peak here Welcome to Param! — param 1.10.0a14 documentation.

@ea42gh and I are working om a promo video and notebook for param. You can check out a preliminary version here https://drive.protonmail.com/urls/K66V127PX8#RacCEK6TjoXa

Hi @Marc !

Thanks for your reply. I had found the New Tasty and Improved docs linked to in one of the posts here and it’s been helpful.

Good intro video! I’d been so focused on the validation part of Param I hadn’t grokked the watcher feature yet - very cool! It’s always a joy how such a good fundamental lego brick can make other things (all your UI packages) so much easier to implement.

In prior work I implemented a lab test automation system that was very modular, hierarchically composable and allowed users to implement interface and control logic for new hardware very easily - getting the low level legos ‘right’ is what made that possible.

2 Likes

Hi @zworkinspace! We’ve planned for unit support since the very early days of Param, and it became issue #5 (Integrate unit support? · Issue #5 · holoviz/param · GitHub) in 2012 shortly after we moved development to github. That issue mentions that @philippjfr had implemented it, but I thought @jstevens did (or maybe he also did?). For a variety of reasons we never did quite get it together in a general way suitable for including in Param, but it’s still something we should do! Last I looked we had planned to use the pint package, which is a separate project that’s easy to incorporate; hope that would work as well as astropy.units?

Hi @jbednar! Great to hear (and not surprising!) that units support has been on the radar from early days. I will take a look at pint and see what it offers.

The astropy.units would be a much larger dependency. However, since we are in the space flight dynamics business, we already use astropy and there is some familiarity and acceptance of it.

The “meta issue” is making it easy(ier?) (or at least documented how) to extend Param with any new type, albeit perhaps requiring the type have defined methods of verification and dynamic interface - since your community has done that for all the currently available types. If I manage to create e.g. a new “class Quantity(Dynamic)” that at least alpha works it’ll be a first step towards that for newbies.

ok, pint looks pretty cool! It’s got parsecs and AU and the extensible unit registry is a big plus. After all, some of us do like to measure things in smoots!

Is there an alpha branch that has pint + Param?

cheers!

As Jim says, it has been a long time since we thought about unit support in param!

One of the big discussions we’ve had in the past is how units in param would then propagate through our other libraries (e.g. HoloViews) and how it may overlap in concept with (for instance) HoloViews Dimension objects. All we have right now is a way of specifying units as a simple string on dimensions.

I do remember discussing whether we could use pint to achieve proper unit support but we didn’t get very far. We also discussed astropy.units but (as you say), it is a much larger dependency though it does seem to get used more in practice.

I’ve always wanted proper unit support but unfortunately I’ve never found a compelling way to get there. Maybe it is time to re-examine the issue as the ecosystem may have evolved (though afaik there still isn’t a completely standard and accepted way of working with units in Python…yet)

Personally, I’d suggest adding a slot for an optional “unit” to the Number hierarchy (not to a copy of it), and change the getters to return the value with a unit attached, and the setters to accept and store the unit. Similar support could then be added to param.Array and maybe in a couple of other places. I don’t think it needs to be a major change. In HoloViews it would be a major change, because we do a lot of arithmetic on quantities that now would have units, but in Param itself it seems relatively straightforward. There may be some branch somewhere with draft code, but it wouldn’t be usable at this point.

My story is similar to @zworkinspace`s. So, thank you for the great package and all the other HoloViz goodies.

Hi @zworkinspace,

thank you for the question, I am struggling with the same issue. My solution is to (ab)use the label attribute with named tuples. It looks like this:


from collections import namedtuple

import param

import pint

import matplotlib.pyplot as plt

ureg = pint.UnitRegistry()

# symbol is for use in plots

Label = namedtuple("Label", "symbol unit")

v_label = Label("v_{max.}", ureg.km/ureg.hour)

p_label = Label("P_{nom.}", ureg.W)

tau_label = Label("\\tau_{nom.}", ureg.N*ureg.m)

class Car(param.Parameterized):

    speed = param.Number(bounds=(0, None), doc="Top speed", label=v_label)

    power = param.Number(bounds=(0, None), doc="Nominal power", label=p_label)

    torque = param.Number(bounds=(0, None), doc="Nominal torque", label=tau_label)

car = Car()

# Pint formatting: https://pint.readthedocs.io/en/stable/tutorial.html

print(f'{car.param["torque"].label.unit:~P}')

# Plot

plt.plot()

plt.title(f"${car.param['speed'].label.symbol}\,[{car.param['speed'].label.unit:~P}]$")

plt.xlabel(f"${car.param['torque'].label.symbol}\,[{car.param['torque'].label.unit:~P}]$")

plt.ylabel(f"${car.param['power'].label.symbol}\,[{car.param['power'].label.unit:~P}]$")

Are there any drawbacks to this approach? Please let me know.