Skip to content

Commit

Permalink
Add fine-grained Monte Carlo control (#376)
Browse files Browse the repository at this point in the history
* Add MC 'include' booleans to allow fine-grained control

* Add additional checkboxes to the MC tab layout

* Generate random or use original params amounts vector
  • Loading branch information
dgdekoning authored Feb 28, 2020
1 parent 7489ea6 commit 4be5d08
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 33 deletions.
59 changes: 38 additions & 21 deletions activity_browser/app/bwutils/montecarlo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from time import time
from typing import Optional, Union

import brightway2 as bw
from bw2calc.utils import get_seed
Expand All @@ -23,10 +24,17 @@ def __init__(self, cs_name):
self.seed = None
self.cf_rngs = {}
self.CF_rng_vectors = {}
self.include_parameters = False
self.include_technosphere = True
self.include_biosphere = True
self.include_cfs = True
self.include_parameters = True
self.param_rng = None
self.param_cols = ["input", "output", "type"]

self.tech_rng: Optional[Union[MCRandomNumberGenerator, np.ndarray]] = None
self.bio_rng: Optional[Union[MCRandomNumberGenerator, np.ndarray]] = None
self.cf_rng: Optional[Union[MCRandomNumberGenerator, np.ndarray]] = None

# functional units
self.func_units = cs['inv']
self.rev_fu_index = {i: fu for i, fu in enumerate(self.func_units)}
Expand All @@ -48,42 +56,53 @@ def __init__(self, cs_name):
self.func_key_dict = {m: i for i, m in enumerate(self.func_unit_translation_dict.keys())}
self.func_key_list = list(self.func_key_dict.keys())

# todo: get rid of the below
self.method_dict_list = []
for i, m in enumerate(self.methods):
self.method_dict_list.append({m: i})

self.results = list()
self.results = []

self.lca = bw.LCA(demand=self.func_units_dict, method=self.methods[0])

def load_data(self):
def load_data(self) -> None:
"""Constructs the random number generators for all of the matrices that
can be altered by uncertainty.
If any of these uncertain calculations are not included, the initial
amounts of the 'params' matrices are used in place of generating
a vector
"""
self.lca.load_lci_data()
self.lca.tech_rng = MCRandomNumberGenerator(self.lca.tech_params, seed=self.seed)
self.lca.bio_rng = MCRandomNumberGenerator(self.lca.bio_params, seed=self.seed)

self.tech_rng = MCRandomNumberGenerator(self.lca.tech_params, seed=self.seed) \
if self.include_technosphere else self.lca.tech_params["amount"].copy()
self.bio_rng = MCRandomNumberGenerator(self.lca.bio_params, seed=self.seed) \
if self.include_biosphere else self.lca.bio_params["amount"].copy()

if self.lca.lcia:
self.cf_rngs = {} # we need as many cf_rng as impact categories, because they are of different size
for m in self.methods:
self.lca.switch_method(m)
self.lca.load_lcia_data()
self.cf_rngs[m] = MCRandomNumberGenerator(self.lca.cf_params, seed=self.seed)
self.cf_rngs[m] = MCRandomNumberGenerator(self.lca.cf_params, seed=self.seed) \
if self.include_cfs else self.lca.cf_params["amount"].copy()
# Construct the MC parameter manager
if self.include_parameters:
self.param_rng = MonteCarloParameterManager(seed=self.seed)

def calculate(self, iterations=10, seed: int = None, parameters: bool = False):
def calculate(self, iterations=10, seed: int = None, **kwargs):
"""Main calculate method for the MC LCA class, allows fine-grained control
over which uncertainties are included when running MC sampling.
"""
start = time()
self.seed = seed or get_seed()
self.include_parameters = parameters
self.include_technosphere = kwargs.get("technosphere", True)
self.include_biosphere = kwargs.get("biosphere", True)
self.include_cfs = kwargs.get("cf", True)
self.include_parameters = kwargs.get("parameters", True)

self.load_data()
self.results = np.zeros((iterations, len(self.func_units), len(self.methods)))

for iteration in range(iterations):
if not hasattr(self.lca, "tech_rng"):
self.load_data()

tech_vector = self.lca.tech_rng.next()
bio_vector = self.lca.bio_rng.next()
tech_vector = self.tech_rng.next() if self.include_technosphere else self.tech_rng
bio_vector = self.bio_rng.next() if self.include_biosphere else self.bio_rng
if self.include_parameters:
param_exchanges = self.param_rng.next()
# combination of 'input', 'output', 'type' columns is unique
Expand All @@ -103,7 +122,7 @@ def calculate(self, iterations=10, seed: int = None, parameters: bool = False):
# pre-calculating CF vectors enables the use of the SAME CF vector for each FU in a given run
cf_vectors = {}
for m in self.methods:
cf_vectors[m] = self.cf_rngs[m].next()
cf_vectors[m] = self.cf_rngs[m].next() if self.include_cfs else self.cf_rngs[m]

# lca_scores = np.zeros((len(self.func_units), len(self.methods)))

Expand All @@ -122,8 +141,6 @@ def calculate(self, iterations=10, seed: int = None, parameters: bool = False):
print('CSMonteCarloLCA: finished {} iterations for {} functional units and {} methods in {} seconds.'.format(
iterations, len(self.func_units), len(self.methods), time() - start
))
# self.results.append(lca_scores)
# self.results[(method, func_unit)] = lca_scores

@property
def func_units_dict(self) -> dict:
Expand Down
42 changes: 30 additions & 12 deletions activity_browser/app/ui/tabs/LCA_results_tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from bw2calc.errors import BW2CalcError
from PySide2.QtWidgets import (
QWidget, QTabWidget, QVBoxLayout, QHBoxLayout, QScrollArea, QRadioButton,
QLabel, QLineEdit, QCheckBox, QPushButton, QComboBox, QTableView, QButtonGroup, QMessageBox
QLabel, QLineEdit, QCheckBox, QPushButton, QComboBox, QTableView,
QButtonGroup, QMessageBox, QGroupBox, QGridLayout
)
from PySide2 import QtGui, QtCore
from stats_arrays.errors import InvalidParamsError
Expand Down Expand Up @@ -919,12 +920,25 @@ def __init__(self, parent):
class MonteCarloTab(NewAnalysisTab):
def __init__(self, parent=None):
super(MonteCarloTab, self).__init__(parent)
self.parent = parent
self.parent: LCAResultsSubTab = parent

self.layout.addLayout(get_header_layout('Monte Carlo Simulation'))
self.scenario_label = QLabel("Scenario:")
self.include_parameters = QCheckBox(self)
self.include_parameters.setChecked(False)
self.include_box = QGroupBox("Include uncertainty for:", self)
grid = QGridLayout()
self.include_tech = QCheckBox("Technosphere", self)
self.include_tech.setChecked(True)
self.include_bio = QCheckBox("Biosphere", self)
self.include_bio.setChecked(True)
self.include_cf = QCheckBox("Characterization Factors", self)
self.include_cf.setChecked(True)
self.include_parameters = QCheckBox("Parameters", self)
self.include_parameters.setChecked(True)
grid.addWidget(self.include_tech, 0, 0)
grid.addWidget(self.include_bio, 0, 1)
grid.addWidget(self.include_cf, 1, 0)
grid.addWidget(self.include_parameters, 1, 1)
self.include_box.setLayout(grid)

self.add_MC_ui_elements()

Expand Down Expand Up @@ -959,7 +973,7 @@ def connect_signals(self):
)

def add_MC_ui_elements(self):
self.layout_mc = QVBoxLayout()
layout_mc = QVBoxLayout()

# H-LAYOUT start simulation
self.button_run = QPushButton('Run Simulation')
Expand All @@ -983,10 +997,9 @@ def add_MC_ui_elements(self):
self.hlayout_run.addWidget(self.iterations)
self.hlayout_run.addWidget(self.label_seed)
self.hlayout_run.addWidget(self.seed)
self.hlayout_run.addWidget(QLabel("Explore parameter uncertainty:"))
self.hlayout_run.addWidget(self.include_parameters)
self.hlayout_run.addWidget(self.include_box)
self.hlayout_run.addStretch(1)
self.layout_mc.addLayout(self.hlayout_run)
layout_mc.addLayout(self.hlayout_run)

# self.label_running = QLabel('Running a Monte Carlo simulation. Please allow some time for this. '
# 'Please do not run another simulation at the same time.')
Expand Down Expand Up @@ -1025,10 +1038,10 @@ def add_MC_ui_elements(self):
self.hlayout_methods.addStretch()
self.method_selection_widget.setLayout(self.hlayout_methods)

self.layout_mc.addWidget(self.method_selection_widget)
layout_mc.addWidget(self.method_selection_widget)
self.method_selection_widget.hide()

self.layout.addLayout(self.layout_mc)
self.layout.addLayout(layout_mc)

def build_export(self, has_table: bool = True, has_plot: bool = True) -> QWidget:
"""Construct the export layout but set it into a widget because we
Expand Down Expand Up @@ -1057,10 +1070,15 @@ def calculate_mc_lca(self):
QMessageBox.warning(self, 'Warning', 'Seed value must be an integer number or left empty.')
self.seed.setText('')
return
include_params = self.include_parameters.isChecked()
includes = {
"technosphere": self.include_tech.isChecked(),
"biosphere": self.include_bio.isChecked(),
"cf": self.include_cf.isChecked(),
"parameters": self.include_parameters.isChecked(),
}

try:
self.parent.mc.calculate(iterations=iterations, seed=seed, parameters=include_params)
self.parent.mc.calculate(iterations=iterations, seed=seed, **includes)
self.update_mc()
except InvalidParamsError as e: # This can occur if uncertainty data is missing or otherwise broken
# print(e)
Expand Down

0 comments on commit 4be5d08

Please sign in to comment.