Skip to content

Commit

Permalink
Add tests for new uncertainty code (#375)
Browse files Browse the repository at this point in the history
* Force closing of pytest_project databases before deleting project

* Add missing pedigree field for uncertainty interface

* add no-cover annotations for abstract class methods

* Simplify UncertaintyDelegate value conversion

* Remember to pass pedigree into the matrix correctly

* Test most of the features of the uncertainty wizard

* Test the uncertainty delegate and remaining interfaces

* Fix for AppVeyor being unruly for some reason.
  • Loading branch information
dgdekoning authored Feb 25, 2020
1 parent 09da23a commit 7489ea6
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 15 deletions.
12 changes: 5 additions & 7 deletions activity_browser/app/bwutils/uncertainty.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class BaseUncertaintyInterface(abc.ABC):
__slots__ = ["_data"]
KEYS = {
"uncertainty type", "loc", "scale", "shape", "minimum", "maximum",
"negative"
"negative", "pedigree"
}
data_type = ""

Expand All @@ -21,22 +21,22 @@ def __init__(self, unc_obj):

@property
def data(self):
return self._data
return self._data # pragma: no cover

@property
@abc.abstractmethod
def amount(self) -> float:
pass
pass # pragma: no cover

@property
@abc.abstractmethod
def uncertainty_type(self) -> UncertaintyBase:
pass
pass # pragma: no cover

@property
@abc.abstractmethod
def uncertainty(self) -> dict:
pass
pass # pragma: no cover


class ExchangeUncertaintyInterface(BaseUncertaintyInterface):
Expand Down Expand Up @@ -70,8 +70,6 @@ def amount(self) -> float:

@property
def uncertainty_type(self) -> UncertaintyBase:
if "uncertainty type" not in self._data.data:
return UndefinedUncertainty
return uc[self._data.data.get("uncertainty type", 0)]

@property
Expand Down
11 changes: 5 additions & 6 deletions activity_browser/app/ui/tables/delegates/uncertainty.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,11 @@ def setEditorData(self, editor: QtWidgets.QComboBox, index: QtCore.QModelIndex):
take the value and set the index in that way.
"""
value = index.data(QtCore.Qt.DisplayRole)
if isinstance(value, (str, float)):
try:
value = int(value)
except ValueError as e:
print("{}, using 0 instead".format(str(e)))
value = 0
try:
value = int(value) if value is not None else 0
except ValueError as e:
print("{}, using 0 instead".format(str(e)))
value = 0
editor.setCurrentIndex(uc.choices.index(uc[value]))

def setModelData(self, editor: QtWidgets.QComboBox, model: QtCore.QAbstractItemModel,
Expand Down
2 changes: 1 addition & 1 deletion activity_browser/app/ui/wizards/uncertainty.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ def initializePage(self):
self.balance_mean_with_loc()
obj = getattr(self.wizard(), "obj")
try:
matrix = PedigreeMatrix.from_dict(obj.uncertainty)
matrix = PedigreeMatrix.from_dict(obj.uncertainty.get("pedigree", {}))
self.pedigree = matrix.factors
except AssertionError as e:
print("Could not extract pedigree data: {}".format(str(e)))
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ environment:
install:
- call %CONDA_INSTALL_LOCN%\Scripts\activate.bat
- conda config --set always_yes yes --set changeps1 no
- conda update -q conda
# - conda update -q conda # Yeet.
- conda info -a
# Install package requirements & test suite
- conda install -q -c conda-forge -c cmutel -c haasad -c pascallesage arrow brightway2 bw2io bw2data eidl fuzzywuzzy matplotlib-base networkx pandas pyside2=5.13 seaborn presamples "pytest>=5.2" pytest-qt pytest-mock
Expand Down
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@ def ab_application():
"""
app = Application()
yield app
# Explicitly close the connection to all the databases for the pytest_project
if bw.projects.current == "pytest_project":
for _, db in bw.config.sqlite3_databases:
if not db._database.is_closed():
db._database.close()
if 'pytest_project' in bw.projects:
bw.projects.delete_project('pytest_project', delete_dir=True)
# finally, perform a cleanup of any remnants, mostly for local testing
bw.projects.purge_deleted_directories()


@pytest.fixture()
Expand Down
100 changes: 100 additions & 0 deletions tests/test_uncertainty.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
"""
Use the existing parameters to look at the uncertainty and edit it in
multiple ways
"""
import brightway2 as bw
from PySide2 import QtCore, QtWidgets
import pytest
from stats_arrays.distributions import UndefinedUncertainty, UniformUncertainty

from activity_browser.app.bwutils.uncertainty import (
ExchangeUncertaintyInterface, CFUncertaintyInterface, get_uncertainty_interface
)
from activity_browser.app.ui.tables.delegates import UncertaintyDelegate
from activity_browser.app.ui.tables.parameters import ProjectParameterTable


def test_table_uncertainty_delegate(qtbot, bw2test, monkeypatch):
""" Open the uncertainty delegate to test all related methods within the table.
"""
table = ProjectParameterTable()
qtbot.addWidget(table)
table.add_parameter()
table.sync(table.build_df())

assert isinstance(table.itemDelegateForColumn(3), UncertaintyDelegate)

delegate = UncertaintyDelegate(table)
option = QtWidgets.QStyleOptionViewItem()
option.rect = QtCore.QRect(0, 0, 100, 100)
index = table.proxy_model.index(0, 3)
rect = table.visualRect(index)
qtbot.mouseClick(table.viewport(), QtCore.Qt.LeftButton, pos=rect.center())
editor = delegate.createEditor(table, option, index)
qtbot.addWidget(editor)

# Test displayText
assert delegate.displayText("1", None) == "No uncertainty"
assert delegate.displayText("nan", None) == "Undefined or unknown uncertainty"

delegate.setEditorData(editor, index)
delegate.setModelData(editor, table.proxy_model, index)

monkeypatch.setattr(QtCore.QModelIndex, "data", lambda *args, **kwargs: "nan")
delegate.setEditorData(editor, index)


def test_exchange_interface(qtbot, ab_app):
flow = bw.Database(bw.config.biosphere).random()
db = bw.Database("testdb")
act_key = ("testdb", "act_unc")
db.write({
act_key: {
"name": "act_unc",
"unit": "kilogram",
"exchanges": [
{"input": act_key, "amount": 1, "type": "production"},
{"input": flow.key, "amount": 2, "type": "biosphere"},
]
}
})

act = bw.get_activity(act_key)
exc = next(e for e in act.biosphere())
interface = get_uncertainty_interface(exc)
assert isinstance(interface, ExchangeUncertaintyInterface)
assert interface.amount == 2
assert interface.uncertainty_type == UndefinedUncertainty
assert interface.uncertainty == {}


@pytest.mark.xfail(reason="Selected CF was already uncertain")
def test_cf_interface(qtbot, ab_app):
key = bw.methods.random()
method = bw.Method(key).load()
cf = next(f for f in method)

assert isinstance(cf, tuple)
if isinstance(cf[-1], dict):
cf = method[1]
assert isinstance(cf[-1], float)
amount = cf[-1] # last value in the CF should be the amount.

interface = get_uncertainty_interface(cf)
assert isinstance(interface, CFUncertaintyInterface)
assert not interface.is_uncertain # CF should not be uncertain.
assert interface.amount == amount
assert interface.uncertainty_type == UndefinedUncertainty
assert interface.uncertainty == {}

# Now add uncertainty.
uncertainty = {"minimum": 1, "maximum": 18, "uncertainty type": UniformUncertainty.id}
uncertainty["amount"] = amount
cf = (cf[0], uncertainty)
interface = get_uncertainty_interface(cf)
assert isinstance(interface, CFUncertaintyInterface)
assert interface.is_uncertain # It is uncertain now!
assert interface.amount == amount
assert interface.uncertainty_type == UniformUncertainty
assert interface.uncertainty == {"uncertainty type": UniformUncertainty.id, "minimum": 1, "maximum": 18}
Loading

0 comments on commit 7489ea6

Please sign in to comment.