Skip to content

Commit

Permalink
Merge pull request #25 from openscm/n2o-clarity
Browse files Browse the repository at this point in the history
Remove N2O ambiguity
  • Loading branch information
znicholls authored May 27, 2021
2 parents 18aa4ac + e721d26 commit a8f1483
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Changelog

master
------

- (`#25 <https://github.com/openscm/openscm-units/pull/25>`_) Add "N2O_conversions" context to remove ambiguity in N2O conversions
- (`#23 <https://github.com/openscm/openscm-units/pull/23>`_) Add AR5 GWPs with climate-carbon cycle feedbacks (closes `#22 <https://github.com/openscm/openscm-units/issues/22>`_)
- (`#20 <https://github.com/openscm/openscm-units/pull/20>`_) Make ``openscm_units.data`` a module by adding an ``__init__.py`` file to it and add docs for ``openscm_units.data`` (closes `#19 <https://github.com/openscm/openscm-units/issues/19>`_)
- (`#18 <https://github.com/openscm/openscm-units/pull/18>`_) Made NH3 a separate dimension to avoid accidental conversion to CO2 in GWP contexts. Also added an ``nh3_conversions`` context to convert to nitrogen (closes `#12 <https://github.com/openscm/openscm-units/issues/12>`_)
Expand Down
52 changes: 40 additions & 12 deletions src/openscm_units/_unit_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@
- fairly obvious ones e.g. carbon dioxide emissions can be provided in 'C' or 'CO2' and
converting between the two is possible
- less obvious ones e.g. nitrous oxide emissions can be provided in 'N', 'N2O' or
'N2ON' (a short-hand which indicates that only the mass of the nitrogen is being counted),
we provide conversions between these three
- less obvious ones e.g. NOx emissions can be provided in 'N' or 'NOx' (a short-hand
which is assumed to be NO2), we provide conversions between these two
- case-sensitivity. In order to provide a simplified interface, using all uppercase
versions of any unit is also valid e.g. ``unit_registry("HFC4310mee")`` is the same as
``unit_registry("HFC4310MEE")``
Expand Down Expand Up @@ -85,6 +84,29 @@
... unit_registry("CH4").to("CO2")
<Quantity(2.75, 'CO2')>
*N2O*
Nitrous oxide emissions are typically reported with units of 'N2O'. However,
they are also reported with units of 'N2ON' (a short-hand which indicates that
only the mass of the nitrogen is being counted). Reporting nitrous oxide
emissions with units of simply 'N' is ambiguous (do you mean the mass of
nitrogen, so 1 N = 28 / 44 N2O or just the mass of a single N atom, so
1 N = 14 / 44 N2O). By default, converting 'N2O' <--> 'N' is forbidden to
prevent this ambiguity. However, the conversion can be performed within the
context 'N2O_conversions', in which case it is assumed that 'N' just means a
single N atom i.e. 1 N = 14 / 44 N2O, as shown below:
.. code:: python
>>> from openscm_units import unit_registry
>>> unit_registry("N2O").to("N")
pint.errors.DimensionalityError: Cannot convert from 'N2O' ([nitrous_oxide]) to 'N' ([nitrogen])
# with a context, the conversion becomes legal again
>>> with unit_registry.context("N2O_conversions"):
... unit_registry("N2O").to("N")
<Quantity(0.318181818, 'N')>
*NOx*
Like for methane, NOx emissions also suffer from a namespace collision. In order to
Expand All @@ -102,11 +124,6 @@
... unit_registry("NOx").to("N")
<Quantity(0.30434782608695654, 'N')>
# as an unavoidable side effect, this also becomes possible
>>> with unit_registry.context("NOx_conversions"):
... unit_registry("NOx").to("N2O")
<Quantity(0.9565217391304348, 'N2O')>
*NH3*
In order to prevent inadvertent conversions from 'NH3' to 'CO2', the conversion
Expand Down Expand Up @@ -144,9 +161,9 @@
"CO2": ["12/44 * C", "carbon_dioxide"],
"CH4": "methane",
"HC50": ["CH4"],
"N2O": "nitrous_oxide",
"N2ON": ["44/28 * N2O", "nitrous_oxide_farming_style"],
"N": "nitrogen",
"N2O": ["14/44 * N", "nitrous_oxide"],
"N2ON": ["14/28 * N", "nitrous_oxide_farming_style"],
"NO2": ["14/46 * N", "nitrogen_dioxide"],
# aerosol precursors
"NOx": "NOx",
Expand Down Expand Up @@ -379,16 +396,27 @@ def _load_contexts(self):
)
self.add_context(_ch4_context)

_n2o_context = pint.Context("NOx_conversions")
_n2o_context = pint.Context("N2O_conversions")
_n2o_context = self._add_transformations_to_context(
_n2o_context,
"[nitrous_oxide]",
self.nitrous_oxide,
"[nitrogen]",
self.nitrogen,
14 / 44,
)
self.add_context(_n2o_context)

_nox_context = pint.Context("NOx_conversions")
_nox_context = self._add_transformations_to_context(
_nox_context,
"[nitrogen]",
self.nitrogen,
"[NOx]",
self.NOx,
(14 + 2 * 16) / 14,
)
self.add_context(_n2o_context)
self.add_context(_nox_context)

_nh3_context = pint.Context("NH3_conversions")
_nh3_context = self._add_transformations_to_context(
Expand Down
37 changes: 24 additions & 13 deletions tests/unit/test_units.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@ def test_base_unit():

def test_nitrogen():
N = unit_registry("N")
np.testing.assert_allclose(N.to("N2ON").magnitude, 28 / 14)
np.testing.assert_allclose(N.to("NO2").magnitude, 46 / 14)

# can only convert to N with right context
with pytest.raises(DimensionalityError):
N.to("N2ON")

with unit_registry.context("N2O_conversions"):
np.testing.assert_allclose(N.to("N2ON").magnitude, 28 / 14)
np.testing.assert_allclose(N.to("N2O").magnitude, 44 / 14)


def test_nox():
NOx = unit_registry("NOx")
Expand All @@ -42,8 +49,8 @@ def test_nox():
np.testing.assert_allclose(N.to("NOx").magnitude, 46 / 14)
np.testing.assert_allclose(NO2.to("NOx").magnitude, 1)
np.testing.assert_allclose(NOx.to("NO2").magnitude, 1)
# this also becomes allowed, unfortunately...
np.testing.assert_allclose(NOx.to("N2O").magnitude, 44 / 46)
with pytest.raises(DimensionalityError):
NOx.to("N2O")


def test_ammonia():
Expand Down Expand Up @@ -149,32 +156,36 @@ def test_a():

def test_context():
CO2 = unit_registry("CO2")
N = unit_registry("N")
N2ON = unit_registry("N2ON")
N2O = unit_registry("N2O")
with unit_registry.context("AR4GWP100"):
np.testing.assert_allclose(CO2.to("N").magnitude, 14 / (44 * 298))
np.testing.assert_allclose(N.to("CO2").magnitude, 44 * 298 / 14)
np.testing.assert_allclose(CO2.to("N2ON").magnitude, 28 / (44 * 298))
np.testing.assert_allclose(N2ON.to("CO2").magnitude, 44 * 298 / 28)

np.testing.assert_allclose(CO2.to("N2O").magnitude, 1 / 298)
np.testing.assert_allclose(N2O.to("CO2").magnitude, 298)


def test_context_with_magnitude():
CO2 = 1 * unit_registry("CO2")
N = 1 * unit_registry("N")
N2ON = 1 * unit_registry("N2ON")
with unit_registry.context("AR4GWP100"):
np.testing.assert_allclose(CO2.to("N").magnitude, 14 / (44 * 298))
np.testing.assert_allclose(N.to("CO2").magnitude, 44 * 298 / 14)
np.testing.assert_allclose(CO2.to("N2ON").magnitude, 28 / (44 * 298))
np.testing.assert_allclose(N2ON.to("CO2").magnitude, 44 * 298 / 28)


def test_context_compound_unit():
CO2 = 1 * unit_registry("kg CO2 / yr")
N = 1 * unit_registry("kg N / yr")
N2ON = 1 * unit_registry("kg N2ON / yr")
with unit_registry.context("AR4GWP100"):
np.testing.assert_allclose(CO2.to("kg N / yr").magnitude, 14 / (44 * 298))
np.testing.assert_allclose(N.to("kg CO2 / yr").magnitude, 44 * 298 / 14)
np.testing.assert_allclose(CO2.to("kg N2ON / yr").magnitude, 28 / (44 * 298))
np.testing.assert_allclose(N2ON.to("kg CO2 / yr").magnitude, 44 * 298 / 28)


def test_context_dimensionality_error():
CO2 = unit_registry("CO2")
with pytest.raises(DimensionalityError):
CO2.to("N")
CO2.to("N2O")


@pytest.mark.parametrize(
Expand Down

0 comments on commit a8f1483

Please sign in to comment.