JScience: Create a unit that is defined as a combination of multiple variables

Pilot_51 :

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 :

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=161841&siteId=1