r-quantities / units

Measurement units for R
https://r-quantities.github.io/units
173 stars 27 forks source link

What is the best way to detect if units have already been installed? #299

Closed billdenney closed 2 years ago

billdenney commented 2 years ago

Related to #146, is there a way to find a list of all valid units (in addition to the defaults with valid_udunits()) or to find if a specific unit has been installed?

I ask because I'm creating a library that uses medical data to perform calculations. I need to be able to perform unit conversions for multiple analytes. For example, I need to be able to convert calcium concentrations in molar or mass units to the other, and I need to be able to convert magnesium concentrations in molar or mass units to the other.

To do this, I've added new units as follows:

units::install_unit("g_ammonia")
units::install_unit("mol_ammonia", def="17.031 g_ammonia")
units::install_unit("g_calcium")
units::install_unit("g_magnesium")
units::install_unit("mol_calcium", def="40.0784 g_calcium")
units::install_unit("mol_magnesium", def="24.305 g_magnesium")

I set these up while loading my package as part of the .onLoad function.

While testing my package devtools::load_all() a second time, it will rerun the code to add the units above, and I get the (expected) error:

> devtools::load_all()
i Loading equil2
 Error in R_ut_map_symbol_to_unit(symbol, ut_unit) : 
"g_ammonia" already maps to existing but different unit

What is the best way to detect if units have already been installed to prevent that error?

Enchufa2 commented 2 years ago

We are able to compile the output of valid_udunits because we directly parse the XML that is fed into udunits on load. But, unfortunately, the udunits API does not provide any means to list the installed units once a units system is in place.

You could put your installation commands into a try block, or you could use units::ud_is_convertible. The safest approach (e.g., suppose that the user already defined some of these symbols and they are wrong) is to remove everything first (this always succeeds) and then proceed with the installation:

library(units)
#> udunits database from /usr/share/udunits/udunits2.xml

for (i in 1:3) {
  remove_unit(paste("g", c("ammonia", "calcium", "magnesium"), sep="_"))
  remove_unit(paste("mol", c("ammonia", "calcium", "magnesium"), sep="_"))
  install_unit("g_ammonia")
  install_unit("mol_ammonia", def="17.031 g_ammonia")
  install_unit("g_calcium")
  install_unit("g_magnesium")
  install_unit("mol_calcium", def="40.0784 g_calcium")
  install_unit("mol_magnesium", def="24.305 g_magnesium")
}

set_units(as_units("mol_magnesium"), "g_magnesium")
#> 24.305 [g_magnesium]
billdenney commented 2 years ago

@Enchufa2 , Remove first and then add back makes sense. I had a try block, but you're right that a user could have a modified set of units in place.

Thanks for the quick and detailed reply!