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

Utilities dimensions #495

Merged
merged 17 commits into from
Apr 2, 2021
Merged

Utilities dimensions #495

merged 17 commits into from
Apr 2, 2021

Conversation

PavelBal
Copy link
Member

@msevestre No tests so far.

#' of both dimensions.
#' If the values cannot be transformed between the dimensions, an error is thrown.
#' @export
dimensionsConversionFactor <- function(dimension1, dimension2, mw = NULL) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very useful if you want e.g. to plot your data in molar or mass concentrations. Is there a better way to check if the dimensions can be converted than to explicitly consider every possible use case?

@PavelBal
Copy link
Member Author

Maybe some things can be done better (using some .NET methods)?

@PavelBal
Copy link
Member Author

Something is broken here..

@msevestre
Copy link
Member

@PavelBal I have merge develop into the code here. Still it won't work but we have a clean slate

DESCRIPTION Outdated Show resolved Hide resolved
#' #' @include enum.R
#' #'
#' #' @export
#' Dimensions <- enum(allAvailableDimensions())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a problem here. It is being loaded directly before initialization. This works when you load ospsuite.core first but does not work in ospsuite.core

validateUnit(fromUnit, dimension)
validateUnit(toUnit, dimension)

dimensionTask <- getDimensionTask()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure why you need sthg like this ? toUnit converts already everything from you. .The goal is to abstract exactly all of this away

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to use the conversion capabilities of OSPS without having the simulation objects (quantities). E.g. read the data from excel and plot it in another unit. toUnit requires a Quantity.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope

toUnit <- function(quantityOrDimension, values, targetUnit, molWeight = NULL) {

Specifically the parameter is called quantityOrDimension.

None of the proposed changes regarding reimplementing conversion by hand belong in Core. Helper function to ensure that dimension and unit etc.. exists, yes. The rest probably not :)

#' of both dimensions.
#' If the values cannot be transformed between the dimensions, an error is thrown.
#' @export
dimensionsConversionFactor <- function(dimension1, dimension2, mw = NULL) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh my. what is this monster?

if (is.null(mw)) {
stop(messages$errorCannotConvertDimensionsNoMW(dimension1, dimension2))
}
return(1 * 1e-6 * mw * 1e-3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not something I want to see here. I need to understand the usage of such concept. We have already everything encapsulated in Core

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be happy to see another way. E.g. - I read data in mg/dl and want to convert it to mol/l. How? Without having a Quantity object.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are plenty of method exposted directly in core doing this. Creating a conversation factor from one dimension to another dimension does not make sense BTW. It needs to be from one unit to another unit

In core see

  double[] ConvertToUnit(string dimensionName, string targetUnit, double[] valuesInBaseUnit, double molWeight);
      double[] ConvertToUnit(string dimensionName, string targetUnit, double[] valuesInBaseUnit);

      double[] ConvertToUnit(string dimensionName, string targetUnit, double valueInBaseUnit, double molWeight);
      double[] ConvertToUnit(string dimensionName, string targetUnit, double valueInBaseUnit);

and

 double[] ConvertToBaseUnit(string dimensionName, string displayUnit, double[] valuesInDisplayUnit, double molWeight);
      double[] ConvertToBaseUnit(string dimensionName, string displayUnit, double[] valuesInDisplayUnit);

      double[] ConvertToBaseUnit(string dimensionName, string displayUnit, double valueInDisplayUnit, double molWeight);
      double[] ConvertToBaseUnit(string dimensionName, string displayUnit, double valueInDisplayUnit);

All exposed via the DImensionTag. IT deals EXPLICITELY with those use cases

#'
#' @return String name of the base unit.
#' @export
getBaseUnit <- function(dimension) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should move to core

dimensionTask <- getDimensionTask()
# Get the dimension object
dimension <- rClr::clrCall(dimensionTask, "DimensionByName", enc2utf8(dimension))
unit <- rClr::clrCall(dimension, "get_BaseUnit")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is clrGet...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to

unit <- rClr::clrGet(dimension,") "BaseUnit

#' @param dimension String name of the dimension.
#' @details Check if the provided dimension is supported. If not, throw an error
#' @export
validateDimension <- function(dimension) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should not we have one method (validateDimension or existDimension with a flag instead of two?) we have the same concept already somewhere else

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you show where? I only remember the concept of "isXXX" returning a boolean, and "validateXXX" throwing an error if "isXXX" is false:

isOfType and validateIsOfType

#' @details Check if the unit is valid for the dimension.
#' @export
hasUnit <- function(unit, dimension) {
validateIsString(c(unit, dimension))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should move ot core too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

Copy link
Member

@msevestre msevestre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sitll needs to be moved to utilities-units

#' @param dimension String name of the dimension.
#' @details Returns \code{TRUE} if the provided dimension is supported otherwise \code{FALSE}
#' @export
hasDimension <- function(dimension) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PavelBal I have renamed this to hasDimension and it is using the core method

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you prefer the core method? We still have the enum Dimensions (or do you think we do not need it? I would disagree) which is created by calling allAvailableDimensions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it called HasDimension btw? What does or does not have the dimension? :D @msevestre

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are looking at this from the system point of view " Does OSPSUITE HAVE The dimension xxx"
so if(ospsuite::hasDimension("Mass")) reads better than if(ospsuite::existsDImension("Mass")) in my opinion

But this is also because we had hasUnit. and not existsUnit. Naming is hard and consistency is even harder

validateIsString(unit)
validateDimension(dimension)
dimensionTask <- getDimensionTask()
rClr::clrCall(dimensionTask, "HasUnit", enc2utf8(dimension), enc2utf8(unit))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usign hasUnit from core

getBaseUnit <- function(dimension) {
validateDimension(dimension)
dimensionTask <- getDimensionTask()
rClr::clrCall(dimensionTask, "BaseUnitFor", enc2utf8(dimension))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using baseUnitFor from core

if (is.null(mw)) {
stop(messages$errorCannotConvertDimensionsNoMW(dimension1, dimension2))
}
return(1 * 1e-6 * mw * 1e-3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are plenty of method exposted directly in core doing this. Creating a conversation factor from one dimension to another dimension does not make sense BTW. It needs to be from one unit to another unit

In core see

  double[] ConvertToUnit(string dimensionName, string targetUnit, double[] valuesInBaseUnit, double molWeight);
      double[] ConvertToUnit(string dimensionName, string targetUnit, double[] valuesInBaseUnit);

      double[] ConvertToUnit(string dimensionName, string targetUnit, double valueInBaseUnit, double molWeight);
      double[] ConvertToUnit(string dimensionName, string targetUnit, double valueInBaseUnit);

and

 double[] ConvertToBaseUnit(string dimensionName, string displayUnit, double[] valuesInDisplayUnit, double molWeight);
      double[] ConvertToBaseUnit(string dimensionName, string displayUnit, double[] valuesInDisplayUnit);

      double[] ConvertToBaseUnit(string dimensionName, string displayUnit, double valueInDisplayUnit, double molWeight);
      double[] ConvertToBaseUnit(string dimensionName, string displayUnit, double valueInDisplayUnit);

All exposed via the DImensionTag. IT deals EXPLICITELY with those use cases

validateUnit(fromUnit, dimension)
validateUnit(toUnit, dimension)

dimensionTask <- getDimensionTask()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope

toUnit <- function(quantityOrDimension, values, targetUnit, molWeight = NULL) {

Specifically the parameter is called quantityOrDimension.

None of the proposed changes regarding reimplementing conversion by hand belong in Core. Helper function to ensure that dimension and unit etc.. exists, yes. The rest probably not :)

@codecov-io
Copy link

Codecov Report

Merging #495 (2d6d243) into develop (4c3654b) will decrease coverage by 3.52%.
The diff coverage is 1.78%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #495      +/-   ##
===========================================
- Coverage    88.52%   85.00%   -3.53%     
===========================================
  Files           72       73       +1     
  Lines         1325     1380      +55     
===========================================
  Hits          1173     1173              
- Misses         152      207      +55     
Impacted Files Coverage Δ
R/messages.R 100.00% <ø> (ø)
R/utilities-dimensions.R 0.00% <0.00%> (ø)
R/utilities-units.R 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4c3654b...2d6d243. Read the comment docs.

@@ -18,4 +18,6 @@ initPackage <- function() {
apiConfig$pkParametersFilePath <- filePathFor("OSPSuite.PKParameters.xml")

rClr::clrCallStatic("OSPSuite.R.Api", "InitializeOnce", apiConfig$ref)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows to populate the enum after loading the package..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not like this because it was creating the variable int he PARENT environment which was global. Now what I changed is that I am initializing the variable in a method and so now the variable are not global anymore

#' @param toUnit String name of the unit to convert to
#' @param dimension Dimension of the units (conversion between mass and molar possible when
#' \code{MW} is provided)
#' @param MW Optional molecule weight in g/mol to use when converting, for example, from molar to mass amount or concentration
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think MW should be passed as g/mol, this is the only unit a user expects.
@msevestre

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MW Optional molecule weight in g/mol

yes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes? :D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I see your point. The problem is that we always pass parameters in core unit
height should be dm etc..
Introducing a change like this would not be logical, specifically because the weight generally comes from simulation$molWeightFor which returns in core unit.
I am still not 100% sure I understand why we need such a method. If this is just for the MW, then we could add another flag to toUnit (mw.in.base.unit = true) by default or sthg like this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No its not for the MW, its for conversion of values that are not in base units - and most probably these would be the values that do not come from a simulation (that can be converted with toUnit). It is for data that are defined in other units - loaded from excel, generated by hand, or whatsoever.. And in this case MW most definitely will be in g/mol (even in the data exporter we always assume MW is in g/mol).

I see your point that this will be inconsistent with the rest of methods. But I also do not see the use case for this function with MW being in the base unit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LEt's discuss WEdnesday. I do not understand why a user would not convert an array using toUnit instead of dealing with some weird factors

#' @include enum.R
#'
#' @export
Dimensions <- list()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialize empty and then populate after package is loaded.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like not to rely on this too much for now because @abdullahhamadeh is going to implement a parsign method see #478

@@ -15,7 +76,7 @@
#'
#' valuesInBaseUnit <- toBaseUnit(par, c(1000, 2000, 3000), "ml")
#' @export
toBaseUnit <- function(quantityOrDimension, values, unit, molWeight = NULL) {
toBaseUnit <- function(quantityOrDimension, values, unit, molWeight = NULL, molWeightUnit = NULL) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be consistent, both "toBaseUnit" and "toUnit" allow now molWeight in custom unit.

#' @export
toUnit <- function(quantityOrDimension, values, targetUnit, molWeight = NULL) {
toUnit <- function(quantityOrDimension, values, targetUnit, sourceUnit = NULL, molWeight = NULL, molWeightUnit = NULL) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be the new signature that allows to convert from non-base units.

@@ -74,8 +147,20 @@ toUnit <- function(quantityOrDimension, values, targetUnit, molWeight = NULL) {
}

if (is.null(molWeight)) {
#Convert values to base unit first if the source unit is provided
if (!is.null(sourceUnit)){
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is repeated in both if-cases, but I cannot think of a better solution now.

#' enum of all units for each dimension
#'
#' @export
DimensionsUnits <- list()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this naming for the enum with the units?

@PavelBal
Copy link
Member Author

PavelBal commented Apr 1, 2021

@msevestre I don't get it, why is the build failing? Obviously it somehow uses the old dlls, but I cannot figure out why. It works on my local machine.

@msevestre
Copy link
Member

@PavelBal Ok I am going to check it out

@@ -77,6 +80,8 @@ export(loadAgingDataFromCSV)
export(loadDataRepositoryFromPKML)
export(loadPopulation)
export(loadSimulation)
export(ospDimensions)
export(ospUnits)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PavelBal This is how we decied to name those guys to avoid any conflicts and it's also easy to type to get intellisense

@@ -18,4 +18,6 @@ initPackage <- function() {
apiConfig$pkParametersFilePath <- filePathFor("OSPSuite.PKParameters.xml")

rClr::clrCallStatic("OSPSuite.R.Api", "InitializeOnce", apiConfig$ref)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not like this because it was creating the variable int he PARENT environment which was global. Now what I changed is that I am initializing the variable in a method and so now the variable are not global anymore

validateIsString(unit)
validateDimension(dimension)
dimensionTask <- getDimensionTask()
rClr::clrCall(dimensionTask, "HasUnit", enc2utf8(dimension), encodeUnit(unit))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to be encodeUnit for unuit


initializeDimensionAndUnitLists <- function() {
#This initializes the two lists in the parent environment which is the package environments
ospDimensions <<- getDimensionsEnum()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PavelBal Here is how we do not polute the global namespace (I think...)

@msevestre msevestre merged commit 7d8d39e into develop Apr 2, 2021
@msevestre msevestre deleted the utilities-dimensions branch April 2, 2021 18:12
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

Successfully merging this pull request may close these issues.

3 participants