unitsofmeasurement / uom-demos

Units of Measurement Demos
Other
21 stars 9 forks source link

[question] Emission units #111

Closed splatch closed 6 months ago

splatch commented 6 months ago

Hello, I've created some basic units which I wanted to evaluate for emission tracking calculations. Units are quite basic:

import static tec.uom.se.unit.Units.*;

import javax.measure.Quantity;
import javax.measure.Unit;
import javax.measure.quantity.Energy;
import tec.uom.se.AbstractUnit;
import tec.uom.se.ComparableQuantity;
import tec.uom.se.quantity.Quantities;
import tec.uom.se.unit.AlternateUnit;
import tec.uom.se.unit.MetricPrefix;
import tec.uom.se.unit.ProductUnit;

public class Demo2 {
  public interface Emission extends Quantity<Emission> {} // gas i.e CO2
  public interface EmissionMass extends Quantity<EmissionMass> {} // mass of given gass
  public interface EmissionRatio extends Quantity<EmissionRatio> {} // mass ratio for given energy

  // energy
  public static final Unit<Energy> WH = new ProductUnit<>(WATT.multiply(HOUR));
  public static final Unit<Energy> KWH = MetricPrefix.KILO(WH);
  public static final Unit<Energy> MWH = MetricPrefix.MEGA(WH);

  // emission
  public static final Unit<Emission> CO2 = new AlternateUnit<>(AbstractUnit.ONE, "CO₂");

  // emission mass
  public static final Unit<EmissionMass> KG_CO2 = KILOGRAM.multiply(CO2).asType(EmissionMass.class);

  // emission ratio
  public static final Unit<EmissionRatio> MWH_KG_CO2 = KG_CO2.divide(MWH).asType(EmissionRatio.class);

  public static void main(String[] args) {
    ComparableQuantity<Energy> quantity = Quantities.getQuantity(1, MWH);
    System.out.println("Consumption " + quantity);

    ComparableQuantity<EmissionRatio> emissionRatio = Quantities.getQuantity(800, MWH_KG_CO2);
    System.out.println("Emission rate " + emissionRatio);

    System.out.println("Emission from 1 MWh: " + emissionRatio.multiply(quantity));

    quantity = Quantities.getQuantity(1, KWH);
    System.out.println("Emission from 1 kWh: " + emissionRatio.multiply(quantity));
  }

}

Problem I face is that my calculations work only if I specify operations using a given scale, for example:

    ComparableQuantity<Energy> quantity = Quantities.getQuantity(1, MWH);
    System.out.println("Consumption " + quantity);

    // this value is specific to the energy source or boiler power
    ComparableQuantity<EmissionRatio> emissionRatio = Quantities.getQuantity(800, MWH_KG_CO2);
    System.out.println("Emission rate " + emissionRatio);

    System.out.println("Emission " + emissionRatio.multiply(quantity));
    // produces
    // Consumption 1 M(W·h)
    // Emission rate 800 kg·CO₂/M(W·h)
    // Emission 800.00 kg·CO₂

When I use kWh it results in a wrong result:

    ComparableQuantity<Energy> quantity = Quantities.getQuantity(1, MetricPrefix.KILO(WH));
    System.out.println("Consumption " + quantity);
    ComparableQuantity<EmissionRatio> emissionRatio = Quantities.getQuantity(800, EmissionRatioUnitSystem.MWH_KG_CO2);
    System.out.println("Emission rate " + emissionRatio);
    System.out.println("Emission " + emissionRatio.multiply(quantity));
    // produces
    // Consumption 1 k(W·h)
    // Emission rate 800 kg·CO₂/M(W·h)
    // Emission 800.00 k(W·h)·kg·CO₂/M(W·h)

I thought my issue was caused by old uom lib, however it works same way with more recent releases of si-units. I suspect that I miss-used API. Can I ask for guidance/top how to define units/quantities so they become interoperable?

keilw commented 6 months ago

While I don't know the particular domain, it is possible, the definition of WATTHOUR above might be incorrect. See Wikipedia: Kilowatt-hour Or What is a watt hour (Wh)?

Please have a look at the domain-specific EnergyDemo. There's also a CO2CarDemo.

I am also not sure, why EmissionMass needs to be defined? Isn't it the same as Mass?

Also it looks like you made a mistake in above code snippet, because the constructor (why not use getQuantity here, too?) ComparableQuantity<EmissionRatio> emissionRatio = new EmissionRatio(800, EmissionRatioUnitSystem.MWH_KG_CO2); uses MWH_KG_CO2 and ignores the KILO(WH).

keilw commented 6 months ago

@splatch I took the freedom to move this to domain-specific demos, because it has nothing to do with Indriya, nor does it seem to be a problem in the RI.

splatch commented 6 months ago

@keilw Thank you for quick answer, I don’t mind move. :) The domain is flexible I guess, since these are derivative (maybe even abstract) units. My starting point was indeed car/fuel/consumption demo you linked.

I began with emission statement published by one of institutes within Poland which says that electricity consumption of 1 MWh results in emission of 800 kgCO2. There are some other fractions which are delivered from 1 MWh, defined in a similar way. Purpose of EmissionMass is to keep a relation between a specific gas and mass, based on emission ratio. Ratios are not static (it depends what energy source and its characteristic is), hence another quantity to express relation of MWh to kgCO2

For Wh I've used constant from si-units which should keep it close to base units. the EmissionRatioQuantity was really my experiment to see if I can intercept math operations and i.e. unify multiplier to common energy unit used by emission ratio. It does not influence results - its just an extension of NumberQuantity with debug statements and super calls.

splatch commented 6 months ago

Maybe, a better choice for EmissionMass would be AnnotatedUnit based on a Mass? It didn't work.

I reverted to my experiment with custom Quantity with manual conversion of multiplier to MWh, then I get valid result of 0.8000 kg·CO₂. Conversion code is rather trivial, real question - is it really necessary?

public class EmissionRatio extends DebugQuantity<EmissionRatio> {
  @Override
  public ComparableQuantity<?> multiply(Quantity<?> that) {
    if (that.getUnit().isCompatible(MWH)) {
      Number mwh = that.getUnit().getConverterTo((Unit) MWH).convert(that.getValue());
      ComparableQuantity<Energy> quantity = Quantities.getQuantity(mwh, MWH);
      return super.multiply(quantity);
    }
    return super.multiply(that);
  }
}
splatch commented 6 months ago

Diving more, below code works, but it does not use metric prefixes:

public class ConversionTests {
  public interface Emission extends Quantity<Emission> {} // gas i.e CO2
  public interface EmissionMass extends Quantity<EmissionMass> {} // mass of given gass
  public interface EmissionRatio extends Quantity<EmissionRatio> {} // mass ratio for given energy

  // energy
  public static final Unit<Energy> WH = new ProductUnit<>(WATT.multiply(HOUR));
  public static final Unit<Energy> KWH = MetricPrefix.KILO(WH);
  public static final Unit<Energy> MWH = MetricPrefix.MEGA(WH);

  // emission
  public static final Unit<Emission> CO2 = new AlternateUnit<>(AbstractUnit.ONE, "CO₂");

  public static void main(String[] args) {
    Unit<EmissionMass> massUnit = new AlternateUnit<>(KILOGRAM.multiply(CO2), "kg·CO₂").asType(EmissionMass.class);
    System.out.println("Emission mass unit " + massUnit);
    ComparableQuantity<EmissionMass> massQty = Quantities.getQuantity(1, massUnit);
    System.out.println("Emission mass prime " + massQty);

    Unit<EmissionRatio> emissionRateUnit = massUnit.divide(WH).asType(EmissionRatio.class);
    System.out.println("Emission rate " + emissionRateUnit);
    ComparableQuantity<EmissionRatio> emissionRateQty = Quantities.getQuantity(0.0008, emissionRateUnit);
    System.out.println("Emission rate prime " + emissionRateQty);

    ComparableQuantity<Energy> one_MWh = Quantities.getQuantity(1000000, WH);
    System.out.println("Emission from " + one_MWh + ": " + one_MWh.multiply(emissionRateQty));
    ComparableQuantity<Energy> one_kWh = Quantities.getQuantity(1000, WH);
    System.out.println("Emission from " + one_kWh + ": " + one_kWh.multiply(emissionRateQty));
    ComparableQuantity<Energy> oneWH = Quantities.getQuantity(1, WH);
    System.out.println("Emission from " + oneWH + ": " + oneWH.multiply(emissionRateQty));
/* output
Emission mass unit kg·CO₂
Emission mass prime 1 kg·CO₂
Emission rate kg·CO₂/(W·h)
Emission rate prime 0.0008 kg·CO₂/(W·h)
Emission from 100000 W·h: 800.0000 kg·CO₂
Emission from 1000 W·h: 0.8000 kg·CO₂
Emission from 1 W·h: 0.0008 kg·CO₂
*/
  }

}

Switching last lines to:

    ComparableQuantity<Energy> one_MWh = Quantities.getQuantity(1, MWH);
    System.out.println("Emission from " + one_MWh + ": " + one_MWh.multiply(emissionRateQty));
    ComparableQuantity<Energy> one_kWh = Quantities.getQuantity(1, KWH);
    System.out.println("Emission from " + one_kWh + ": " + one_kWh.multiply(emissionRateQty));
    ComparableQuantity<Energy> oneWH = Quantities.getQuantity(1, WH);
    System.out.println("Emission from " + oneWH + ": " + oneWH.multiply(emissionRateQty));
/* output
Emission mass unit kg·CO₂
Emission mass prime 1 kg·CO₂
Emission rate kg·CO₂/(W·h)
Emission rate prime 0.0008 kg·CO₂/(W·h)
Emission from 1 M(W·h): 0.0008 M(W·h)·kg·CO₂/(W·h)
Emission from 1 k(W·h): 0.0008 k(W·h)·kg·CO₂/(W·h)
Emission from 1 W·h: 0.0008 kg·CO₂
*/

Causes output for 1 MWh and 1 kWh to retain the emission mass unit and apply scaling according to provided defined emission ratio. I suppose this shouldn't be a case, since there is common base unit and metric prefix is just a scale.

keilw commented 6 months ago

Earlier you didn't use those prefixes, in the last patch you did, that is correct. creating the quantities

If you want to use the base unit like WH, you'll have to convert to that unit first, see the last line of:

package tech.uom.demo.energy;

import static tech.units.indriya.unit.Units.*;

import javax.measure.MetricPrefix;
import javax.measure.Quantity;
import javax.measure.Unit;
import javax.measure.quantity.Energy;

import tech.units.indriya.AbstractUnit;
import tech.units.indriya.quantity.Quantities;
import tech.units.indriya.unit.AlternateUnit;
import tech.units.indriya.unit.ProductUnit;

public class EmissionDemo {
      private static interface Emission extends Quantity<Emission> {} // gas i.e CO2
      private static interface EmissionMass extends Quantity<EmissionMass> {} // mass of given gass
      private static interface EmissionRatio extends Quantity<EmissionRatio> {} // mass ratio for given energy

      // energy
      public static final Unit<Energy> WH = new ProductUnit<>(WATT.multiply(HOUR));
      public static final Unit<Energy> KWH = MetricPrefix.KILO(WH);
      public static final Unit<Energy> MWH = MetricPrefix.MEGA(WH);

      // emission
      public static final Unit<Emission> CO2 = new AlternateUnit<>(AbstractUnit.ONE, "CO₂");

      public static void main(String[] args) {
        Unit<EmissionMass> massUnit = new AlternateUnit<>(KILOGRAM.multiply(CO2), "kg·CO₂").asType(EmissionMass.class);
        System.out.println("Emission mass unit " + massUnit);
        Quantity<EmissionMass> massQty = Quantities.getQuantity(1, massUnit);
        System.out.println("Emission mass prime " + massQty);

        Unit<EmissionRatio> emissionRateUnit = massUnit.divide(WH).asType(EmissionRatio.class);
        System.out.println("Emission rate " + emissionRateUnit);
        Quantity<EmissionRatio> emissionRateQty = Quantities.getQuantity(0.0008, emissionRateUnit);
        System.out.println("Emission rate prime " + emissionRateQty);

        Quantity<Energy> one_MWh = Quantities.getQuantity(1000000, WH);
        System.out.println("Emission from " + one_MWh + ": " + one_MWh.multiply(emissionRateQty));
        Quantity<Energy> one_kWh = Quantities.getQuantity(1000, WH);
        System.out.println("Emission from " + one_kWh + ": " + one_kWh.multiply(emissionRateQty));
        Quantity<Energy> oneWH = Quantities.getQuantity(1, WH);
        System.out.println("Emission from " + oneWH + ": " + oneWH.multiply(emissionRateQty));

        Quantity<Energy> true_one_MWh = Quantities.getQuantity(1, MWH);
        System.out.println("Emission from " + true_one_MWh + ": " + true_one_MWh.multiply(emissionRateQty));
        System.out.println("Emission from " + true_one_MWh + ": " + true_one_MWh.to(WH).multiply(emissionRateQty));
        System.out.println("Equivalent? " + true_one_MWh.isEquivalentTo(one_MWh));

    /* output
    Emission mass unit kg·CO₂
    Emission mass prime 1 kg·CO₂
    Emission rate kg·CO₂/(W·h)
    Emission rate prime 8.0E-4 kg·CO₂/(W·h)
    Emission from 1000000 W·h: 800 kg·CO₂
    Emission from 1000 W·h: 0.80000 kg·CO₂
    Emission from 1 W·h: 0.00080 kg·CO₂
    Emission from 1 M(W·h): 0.00080 M(W·h)·kg·CO₂/(W·h)
    MWh Equivalent? true
    Emission from 1 M(W·h): 800 kg·CO₂
    */
      }
}

If you're happy about the contribution, I could add the the slightly tweaked class above to either uom-energy-demos or probably even better the Weather & Climate Demos?

splatch commented 6 months ago

If you're happy about the contribution, I could add the the slightly tweaked class above to either uom-energy-demos or probably even better the Weather & Climate Demos?

I am really happy that my failed attempt can become a real demo. It shows a real power of library and its ability to adopt in multiple cases. I feel ashamed that I haven’t found .to(WH) before! It will be better if I will not share other code I've made to substitute it. ;-)

keilw commented 6 months ago

Great, I will push those changes in the next few days. Right now, there is a problem with Sonatype/Mavencentral, but that has nothing to do with these demos.

The 3 units:

private static interface Emission extends Quantity<Emission> {} // gas i.e CO2
private static interface EmissionMass extends Quantity<EmissionMass> {} // mass of given gas
private static interface EmissionRatio extends Quantity<EmissionRatio> {} // mass ratio for given energy

are not wrong, but they add a bit of complexity and physically it is just Mass.

You might consider something like: final Unit<SpecificCarbonEmission> UNIT_GRAM_CO2_PER_LITRE = Units.GRAM.divide(LITRE).asType(SpecificCarbonEmission.class); from the CO2CarDemo, but theoretically one could also do something like GRAM.multiply(CO2).divide(LITRE.multiply(GASOLINE)).asType(SpecificCarbonEmission.class) ;-)

splatch commented 6 months ago

Sonatype/Mavencentral, but that has nothing to do with these demos.

I’ve tried to use central publishing plugin but it didn't work in any version and portal validation been refusing stuff nexus / implicit staging accepted. So I’ve asked to create profile in s01.oss.sonatype instance and not looked back. Sadly the old claim system seem to be gone now, am I right?

Anyhow, I’ll try to play around the emission stuff to see how much of CO2 my desktop computer produce while conducting ordered work! Cheers!