2

I discovered JScience a couple months ago and it has been a huge help for my project, though I'm struggling with one thing.

I'm trying to create a PressureHead (aka water column) unit that can be converted directly with Length and indirectly with Pressure given a VolumetricDensity value.

To find pressure: Density × Gravity × Head = Pressure

Here is an example conversion from Wikipedia:

1 cmH2O (4°C) = 999.9720 kg/m3 × 9.80665 m/s2 × 1 cm = 98.063754138 Pa

1 cmH2O can be directly converted to 1 cm.

Let's say I know the pressure in Pa and want to find the pressure head in mH2O, which is the conversion I would be doing most often in my project. I'll also need to know the density of the fluid. The pressure and density are variable inputs. Gravity must also be known for the formula, but for my purposes it can be fixed to standard gravity.

To find pressure head: Pressure / (Density × Gravity) = Head

For sake of simplicity, I've just repurposed the values from the above example, multiplying pressure by 100 to get 1 mH2O instead of 1 cmH2O.

9806.3754138 Pa / (999.9720 kg/m3 × 9.80665 m/s2) = 1 mH2O

It looks like JScience may be flexible enough to allow such a unit, but I haven't seen any examples to help me create it. Worst case, I'll probably just settle for converting between them with util methods.

Edit

Some examples of the ideal usage I would like to see:

// Pressure to PressureHead
Amount<Pressure> pressure = Amount.valueOf(9806.3754138, PASCAL);
Amount<VolumetricDensity> density = Amount.valueOf(999.9720, KG_M3);
Amount<PressureHead> head = pressure.to(M_H2O, density);
System.out.println(pressure.doubleValue(M_H2O, density)); // 1.0

// PressureHead <-> Length
Amount<Length> length = head.to(METER);
System.out.println(head.doubleValue(METER)); // 1.0
head = length.to(M_H2O);
System.out.println(length.doubleValue(M_H2O)); // 1.0

// PressureHead to Pressure
pressure = head.to(PASCAL, density);
System.out.println(head.doubleValue(PASCAL, density)); // 9806.3754138

Converting between PressureHead units is easy. I can define additional units like so:

Unit<PressureHead> FT_H2O = M_H2O.transform(FOOT.getConverterTo(METER));

For the ideal usage above, I would need to subclass Amount and overload to() and doubleValue(). I suspect if there's a more proper way of doing the conversions (albeit not as pretty usage), it involves subclassing UnitConverter and/or one of the DerivedUnit-based classes.

Part of me wants to give up and just go the quick and easy (and ugly) route of util methods so I can move on to more important things, the other part of me wants to find a solution that makes me love JScience even more.

Pilot_51
  • 7,337
  • 3
  • 29
  • 26
  • I'm not as familiar with JScience (and its application) as I would like to be, but tried around a bit, and now I'm not sure whether the resulting code makes sense and whether it is what you're looking for... Could you describe (on the level of code) what the expected usage of this unit should look like? – Marco13 Aug 14 '19 at 22:41
  • Edited with some ideal usage examples. – Pilot_51 Aug 15 '19 at 13:29
  • 1
    I haven't worked much with JScience yet and don't know much about certain "best practices" or rules here. But if I understood the update correctly, then you want to introduce a new `Quantity` named `PressureHead`, with the corresponding `Unit`. Adding new methods to `Amount` etc. is probably not much better or worse than utility methods. But I wonder how you want to do the conversion, because it seems like the dimensions simply don't match... – Marco13 Aug 16 '19 at 17:50
  • For now, I'm using a utility method to convert `Pressure` to `PressureHead` and it gets the job done. I have a bunch of tests making sure the calculations match a reliable source and they all pass. I don't really need to convert the other direction, so got rid of the method to do so. Conversion between `PressureHead` and `Length` is required for some math and I do that by using `doubleValue()` on everything, doing the math, and recreating the `Amount` with `valueOf()`. – Pilot_51 Aug 16 '19 at 18:30
  • One point is: The `getDimension` of the pressureHead unit should probably be *Length*. But converting between Pascal (which has dimension `[M]/([L]·[T]²)`) and Length `[L]` is not directly possible. I think that JScience should/could offer a solution for this, but am not sure. Or more directly: Did you intend the unit to be a `Unit`, a `Unit` or a `Unit`? (Syntactically, the latter. But it is in fact a unit of pressure, and measures a length - that's what I cannot wrap my head around :-/ ) – Marco13 Aug 16 '19 at 20:37
  • I originally had it as a `Unit` and converted with PSI using `Unit FT_H2O = PSI.divide(2.3066587368787)`, but since it didn't factor in the slight variances of water density, it wasn't accurate enough and prevented my tests from passing. Pressure head is very similar to `NonSI.MILLIMETER_OF_MERCURY` in how it's a measurement of length of a substance in a tube defined as a pressure. In that case, it's `PASCAL.times(3386.388)`. Changing to PressureHead helped simplify some other code that handles the types of pressure measurements differently. – Pilot_51 Aug 16 '19 at 21:14
  • I think @Marco13 is right about `Quantity`, for [example](https://stackoverflow.com/a/44533782/230513), but you'll also need [_Water Density by Temperature_](https://www.vcalc.com/wiki/MichaelBartmess/Water+Density+by+Temperature) – trashgod Sep 07 '19 at 04:50
  • The density was already determined by temperature before I wanted to convert to PressureHead. Anyway, this whole thing turned out to be more complex than necessary and I had to switch to simpler formulas that don't bother with density. I was asked to make a TDH/NPSHa calculator, so I built and tested against a calculator from a company that specializes in this stuff whose API I need to use for other related purposes in the app, then later given a spreadsheet that is used internally to calculate NPSHa conservatively for customers, which is what my calculator had to be changed to match. – Pilot_51 Sep 07 '19 at 17:11

1 Answers1

0

While I no longer need to do this kind of conversion at the moment, I'm revisiting this issue after converting most of the project (all except UI code) to Kotlin and finding reasons to love Kotlin. I made the switch because I saw it as a good opportunity to learn the language while the project was still in the early stages. As such, this doesn't exactly answer my past self who wanted a Java answer, but my current self is happy accepting this as the answer.

This solution utilizes Kotlin's extension functions.

To prevent the two directions clashing, they must use different function names or be defined in separate objects or packages.

object PressureToHead {
    fun Amount<Pressure>.to(unit: Unit<PressureHead>,
            fluidDensity: Amount<VolumetricDensity>): Amount<PressureHead> {
        return Amount.valueOf(doubleValue(PASCAL)
                / (fluidDensity.doubleValue(KG_M3)
                * SensorManager.GRAVITY_EARTH), M_H2O).to(unit)
    }

    fun Amount<Pressure>.doubleValue(unit: Unit<PressureHead>,
            fluidDensity: Amount<VolumetricDensity>): Double {
        return to(unit, fluidDensity).estimatedValue
    }
}

object HeadToPressure {
    fun Amount<PressureHead>.to(unit: Unit<Pressure>,
            fluidDensity: Amount<VolumetricDensity>): Amount<Pressure> {
        return Amount.valueOf(fluidDensity.doubleValue(KG_M3)
                * SensorManager.GRAVITY_EARTH
                * doubleValue(M_H2O), PASCAL).to(unit)
    }

    fun Amount<PressureHead>.doubleValue(unit: Unit<Pressure>,
            fluidDensity: Amount<VolumetricDensity>): Double {
        return to(unit, fluidDensity).estimatedValue
    }
}

For converting between PressureHead and Length, I couldn't use the same function names because they shadowed the existing ones. The best solution seems to be to just give the functions a different name, which I'm okay with.

object HeadToLength {
    fun Amount<PressureHead>.toLength(unit: Unit<Length>): Amount<Length> {
        return Amount.valueOf(doubleValue(M_H2O), METER).to(unit)
    }

    fun Amount<PressureHead>.doubleValueLength(unit: Unit<Length>): Double {
        return toLength(unit).estimatedValue
    }
}

object LengthToHead {
    fun Amount<Length>.toHead(unit: Unit<PressureHead>): Amount<PressureHead> {
        return Amount.valueOf(doubleValue(METER), M_H2O).to(unit)
    }

    fun Amount<Length>.doubleValueHead(unit: Unit<PressureHead>): Double {
        return toHead(unit).estimatedValue
    }
}

Usage is exactly as I wanted aside from the small difference with the Length conversions. This time written in Kotlin.

// Pressure to PressureHead
val density = Amount.valueOf(999.9720, KG_M3)
var pressure = Amount.valueOf(9806.3754138, PASCAL)
var head = pressure.to(M_H2O, density)
println(pressure.doubleValue(M_H2O, density)) // 1.0

// PressureHead to Pressure
pressure = head.to(PASCAL, density)
println(head.doubleValue(PASCAL, density)) // 9806.3754138

// PressureHead <-> Length
Amount<Length> length = head.toLength(METER)
println(head.doubleValueLength(METER)) // 1.0
head = length.toHead(M_H2O)
println(length.doubleValueHead(M_H2O)) // 1.0
Pilot_51
  • 7,337
  • 3
  • 29
  • 26