Skip to content

Commit

Permalink
Merge pull request #16 from IAMconsortium/issue/15
Browse files Browse the repository at this point in the history
Convert to a Python package
  • Loading branch information
khaeru committed Apr 6, 2020
2 parents 1149a0a + 05b7550 commit 9471509
Show file tree
Hide file tree
Showing 21 changed files with 985 additions and 145 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
@@ -0,0 +1,31 @@
name: Python package

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
- name: Upgrade pip
run: python -m pip install --upgrade pip
- name: Lint with flake8
run: |
pip install flake8
find . -name "*.py" | flake8 --count --max-complexity=5 \
--show-source --statistics
- name: Install and test with pytest
run: |
pip install pytest setuptools-scm
pip install --editable .
pytest --verbose
- name: Test package build
run: |
pip install twine wheel
python3 setup.py bdist_wheel sdist
twine check dist/*
6 changes: 6 additions & 0 deletions .gitignore
@@ -1,4 +1,10 @@
# Python
*.egg-info
__pycache__
.benchmarks
.pytest_cache
build
dist

# Editors
.idea/*
3 changes: 3 additions & 0 deletions AUTHORS
@@ -0,0 +1,3 @@
Paul Natsuo Kishimoto <kishimot@iiasa.ac.at> (@khaeru, IIASA)
Daniel Huppmann <huppmann@iiasa.ac.at> (@danielhuppmann, IIASA)
Franceso Lovat <lovat@iiasa.ac.at> (@francescolovat, IIASA)
38 changes: 38 additions & 0 deletions DEVELOPING.rst
@@ -0,0 +1,38 @@
Development notes
*****************

The repository and package aim to be ruthlessly simple, and thus as easy as possible to maintain.
Thus:

- No built documentation; like `pycountry <https://pypi.org/project/pycountry/>`_, the README *is* the documentation.
- Actual code (in \_\_init\_\_.py) kept to a minimum.
- `setuptools-scm <https://pypi.org/project/setuptools-scm/>`_ and git tags used for all versioning.
- Minimal CI configuration: one service/OS/Python version.
- Versioning: similar to pycountry: ``<YYYY>.<M>.<D>``.
- AUTHORS: anyone adding a commit to the repo should also add their name to AUTHORS.


Test the package build
======================

.. code-block:: shell
$ rm -r build dist
$ python3 setup.py bdist_wheel sdist
$ twine check dist/
Ensure there are no warnings from twine.


Generated data files for GWP contexts
=====================================

iam_units/data/emissions/emissions.txt defines the base units for Pint, and imports the files iam_units/data/emissions/gwp\_\*.txt.
These files each define one context, and contain a notice that they should not be edited manually.

Update these files using the command::

$ python -m iam_units.update emissions

The update submodule parses metric_conversions.csv and writes the context files.
When adding a new context file, make sure to ``@import`` it in emissions.txt and write a unit test.
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

45 changes: 0 additions & 45 deletions README.md

This file was deleted.

122 changes: 122 additions & 0 deletions README.rst
@@ -0,0 +1,122 @@
Unit definitions for integrated-assessment research
***************************************************

© 2020 `IAM-units authors`_; licensed under the `GNU GPL version 3`_.

The file `definitions.txt`_ gives `Pint`_-compatible definitions of energy, climate, and related units to supplement the SI and other units included in Pint's `default_en.txt`_.
These definitions are used by:

- the IIASA Energy Program `MESSAGEix-GLOBIOM`_ integrated assessment model (IAM),
- the Python package `pyam`_ for analysis and visualization of integrated-assessment scenarios (see `pyam.IamDataFrame.convert_unit()`_ for details)

and may be used for research in integrated assessment, energy systems, transportation, or other, related fields.
(Please open a `pull request`_ to add your usage to this README!)

Usage
=====

.. code-block:: python
>>> from iam_units import registry
>>> qty = registry('1.2 tce')
>>> qty
1.2 <Unit('tonne_of_coal_equivalent')>
>>> qty.to('GJ')
29.308 <Unit('gigajoule')>
To make the ``registry`` from this package the default:

.. code-block:: python
>>> import pint
>>> pint.set_application_registry(registry)
# Now used by default for pint top-level classes and methods
>>> pint.Quantity('1.2 tce')
1.2 <Unit('tonne_of_coal_equivalent')>
Warnings
========

``iam_units`` overwrites Pint's default definitions in the following cases:

.. list-table::
:header-rows: 1

- - ``pint`` default
- ``iam_units``
- Note
- - 'C' = Coulomb
- 'C' = carbon
- See `emissions.txt`_ at line 10.

Technical details
=================

Emissions and GWP
-----------------

`emissions.txt`_ defines some greenhouse gases (GHGs) as Pint base units.
Conversion of masses of these GHGs to CO₂ equivalents use selectable global warming potential (GWP) metrics, implemented as Pint `contexts`_ in the other files in the same directory.
The contexts have names like ``gwp_<IPCC report>GWP<years>``, where ``<years>`` is `100` and:

.. list-table::
:header-rows: 1

- - ``<IPCC report>``
- Meaning
- - ``SAR``
- Second Assessment Report (1995)
- - ``AR4``
- Fourth Assessment Report (2007)
- - ``AR5``
- Fifth Assessment Report (2014)

To use one of these contexts, give its name as the second argument to the ``pint.Quantity.to()`` method:

.. code-block:: python
>>> qty = registry('3.5e3 t N20')
>>> qty
3500 <Unit('metric_ton * nitrous_oxide')>
>>> qty.to('Mt CO2', 'gwp_AR4GWP100')
0.9275 <Unit('carbon_dioxide * megametric_ton')>
# Using a different metric
>>> qty.to('Mt CO2', 'gwp_SARGWP100')
1.085 <Unit('carbon_dioxide * megametric_ton')>
Data sources
~~~~~~~~~~~~
The GWP unit definitions are generated using the file metric_conversions.csv.
The file is copied from `lewisjared/scmdata`_ v0.4, authored by `@lewisjared <https://github.com/lewisjared>`_, `@swillner <https://github.com/swillner>`_, and `@znicholls <https://github.com/znicholls>`_ and licensed under BSD-3.
The version in scmdata was transcribed from `this source`_ (PDF link).

See `<DEVELOPING.rst>`_ for details on updating the definitions.

.. _contexts: https://pint.readthedocs.io/en/latest/contexts.html
.. _lewisjared/scmdata: https://github.com/lewisjared/scmdata/tree/v0.4.0/src/scmdata/data
.. _this source: https://www.ghgprotocol.org/sites/default/files/ghgp/Global-Warming-Potential-Values%20%28Feb%2016%202016%29_1.pdf


Tests and development
=====================

Use ``pytest iam_units`` to check that the definitions can be loaded.
Example unit expressions in `checks.csv`_ are also checked.
See `<DEVELOPING.rst>`_ for further details.

.. _IAM-units authors: ./AUTHORS
.. _GNU GPL version 3: ./LICENSE
.. _definitions.txt: ./iam_units/data/definitions.txt
.. _emissions.txt: ./iam_units/data/emissions/emissions.txt
.. _checks.csv: ./iam_units/data/checks.csv
.. _Pint: https://pint.readthedocs.io
.. _default_en.txt: https://github.com/hgrecco/pint/blob/master/pint/default_en.txt
.. _MESSAGEix-GLOBIOM: https://message.iiasa.ac.at
.. _pyam: https://pyam-iamc.readthedocs.io
.. _pyam.IamDataFrame.convert_unit(): https://pyam-iamc.readthedocs.io/en/stable/api/iamdataframe.html#pyam.IamDataFrame.convert_unit
.. _pull request: https://github.com/IAMconsortium/units/pulls
7 changes: 7 additions & 0 deletions iam_units/__init__.py
@@ -0,0 +1,7 @@
from pathlib import Path

import pint

registry = pint.UnitRegistry()
registry.load_definitions(
str(Path(__file__).parent / 'data' / 'definitions.txt'))
File renamed without changes.
2 changes: 1 addition & 1 deletion definitions.txt → iam_units/data/definitions.txt
Expand Up @@ -49,4 +49,4 @@ tkm = tonne_freight * kilometer

# please add any emissions-related definitions to modules/emissions

@import modules/emissions/emissions.txt
@import emissions/emissions.txt
Expand Up @@ -25,7 +25,7 @@ ammonia = [ammonia] = nh3 = NH3
sulfur = [sulfur] = S

# Importing contexts to define conversions to co2-equivalents (=co2)
# see the README of this module for more information
# See DEVELOPING.rst for more information.

@import gwp_AR5GWP100.txt
@import gwp_AR4GWP100.txt
Expand Down
@@ -1,4 +1,4 @@
# This file was created using the script `write_gwp_context.py`
# This file was generated using python -m iam_units.update emissions
# DO NOT ALTER THIS FILE MANUALLY!

@context gwp_AR4GWP100
Expand All @@ -25,4 +25,4 @@
[nitrous_oxide] / [time] -> [carbon_dioxide] / [time]: value * 298.0 * CO2 / N2O
[carbon_dioxide] / [time] -> [nitrous_oxide] / [time]: value / 298.0 * N2O / CO2

@end
@end
@@ -1,4 +1,4 @@
# This file was created using the script `write_gwp_context.py`
# This file was generated using python -m iam_units.update emissions
# DO NOT ALTER THIS FILE MANUALLY!

@context gwp_AR5GWP100
Expand All @@ -25,4 +25,4 @@
[nitrous_oxide] / [time] -> [carbon_dioxide] / [time]: value * 265.0 * CO2 / N2O
[carbon_dioxide] / [time] -> [nitrous_oxide] / [time]: value / 265.0 * N2O / CO2

@end
@end
@@ -1,4 +1,4 @@
# This file was created using the script `write_gwp_context.py`
# This file was generated using python -m iam_units.update emissions
# DO NOT ALTER THIS FILE MANUALLY!

@context gwp_SARGWP100
Expand All @@ -25,4 +25,4 @@
[nitrous_oxide] / [time] -> [carbon_dioxide] / [time]: value * 310.0 * CO2 / N2O
[carbon_dioxide] / [time] -> [nitrous_oxide] / [time]: value / 310.0 * N2O / CO2

@end
@end
File renamed without changes.
18 changes: 6 additions & 12 deletions test.py → iam_units/test_all.py
Expand Up @@ -3,16 +3,18 @@

import pint
from pint.util import UnitsContainer

import pytest

from iam_units import registry


DATA_PATH = Path(__file__).parent / 'data'
defaults = pint.get_application_registry()


# Read units to check from file
PARAMS = []
with open(Path(__file__).with_name('checks.csv')) as f:
with open(DATA_PATH / 'checks.csv') as f:
for row in csv.reader(f, skipinitialspace=True, quoting=csv.QUOTE_MINIMAL):
try:
unit_str, dims, new_def = row
Expand All @@ -33,17 +35,9 @@
PARAMS.append((unit_str, dims, new_def))


@pytest.fixture(scope='session')
def registry():
"""UnitRegistry including definitions from definitions.txt."""
reg = pint.UnitRegistry()
reg.load_definitions(str(Path(__file__).with_name('definitions.txt')))
yield reg


@pytest.mark.parametrize('unit_str, dim, new_def', PARAMS,
ids=lambda v: v if isinstance(v, str) else '')
def test_units(registry, unit_str, dim, new_def):
def test_units(unit_str, dim, new_def):
if new_def:
# Units defined in dimensions.txt are not recognized by base pint
with pytest.raises(pint.UndefinedUnitError):
Expand All @@ -60,7 +54,7 @@ def test_units(registry, unit_str, dim, new_def):
[('AR5GWP100', 28),
('AR4GWP100', 25),
('SARGWP100', 21)])
def test_units_emissions(registry, context, value):
def test_units_emissions(context, value):
# The registry shouldn't convert with specifying a valid context
with pytest.raises(pint.DimensionalityError):
registry['ch4'].to('co2')
Expand Down

0 comments on commit 9471509

Please sign in to comment.