-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
105 changed files
with
3,445 additions
and
2,162 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
from .activity.activity_relink import ActivityRelink | ||
from .activity.activity_new import ActivityNew | ||
from .activity.activity_duplicate import ActivityDuplicate | ||
from .activity.activity_open import ActivityOpen | ||
from .activity.activity_graph import ActivityGraph | ||
from .activity.activity_duplicate_to_loc import ActivityDuplicateToLoc | ||
from .activity.activity_delete import ActivityDelete | ||
from .activity.activity_duplicate_to_db import ActivityDuplicateToDB | ||
|
||
from .calculation_setup.cs_new import CSNew | ||
from .calculation_setup.cs_delete import CSDelete | ||
from .calculation_setup.cs_duplicate import CSDuplicate | ||
from .calculation_setup.cs_rename import CSRename | ||
|
||
from .database.database_import import DatabaseImport | ||
from .database.database_export import DatabaseExport | ||
from .database.database_new import DatabaseNew | ||
from .database.database_delete import DatabaseDelete | ||
from .database.database_duplicate import DatabaseDuplicate | ||
from .database.database_relink import DatabaseRelink | ||
|
||
from .exchange.exchange_new import ExchangeNew | ||
from .exchange.exchange_delete import ExchangeDelete | ||
from .exchange.exchange_modify import ExchangeModify | ||
from .exchange.exchange_formula_remove import ExchangeFormulaRemove | ||
from .exchange.exchange_uncertainty_modify import ExchangeUncertaintyModify | ||
from .exchange.exchange_uncertainty_remove import ExchangeUncertaintyRemove | ||
from .exchange.exchange_copy_sdf import ExchangeCopySDF | ||
|
||
from .method.method_duplicate import MethodDuplicate | ||
from .method.method_delete import MethodDelete | ||
|
||
from .method.cf_uncertainty_modify import CFUncertaintyModify | ||
from .method.cf_amount_modify import CFAmountModify | ||
from .method.cf_remove import CFRemove | ||
from .method.cf_new import CFNew | ||
from .method.cf_uncertainty_remove import CFUncertaintyRemove | ||
|
||
from .parameter.parameter_new import ParameterNew | ||
from .parameter.parameter_new_automatic import ParameterNewAutomatic | ||
from .parameter.parameter_rename import ParameterRename | ||
|
||
from .project.project_new import ProjectNew | ||
from .project.project_duplicate import ProjectDuplicate | ||
from .project.project_delete import ProjectDelete | ||
|
||
from .default_install import DefaultInstall | ||
from .biosphere_update import BiosphereUpdate | ||
from .plugin_wizard_open import PluginWizardOpen | ||
from .settings_wizard_open import SettingsWizardOpen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from typing import Union, Callable, List | ||
|
||
from PySide2 import QtWidgets, QtCore | ||
|
||
from activity_browser import application, activity_controller | ||
from activity_browser.ui.icons import qicons | ||
from activity_browser.actions.base import ABAction | ||
|
||
|
||
class ActivityDelete(ABAction): | ||
""" | ||
ABAction to delete one or multiple activities if supplied by activity keys. Will check if an activity has any | ||
downstream processes and ask the user whether they want to continue if so. Exchanges from any downstream processes | ||
will be removed | ||
""" | ||
icon = qicons.delete | ||
title = 'Delete ***' | ||
activity_keys: List[tuple] | ||
|
||
def __init__(self, activity_keys: Union[List[tuple], Callable], parent: QtCore.QObject): | ||
super().__init__(parent, activity_keys=activity_keys) | ||
|
||
def onTrigger(self, toggled): | ||
# retrieve activity objects from the controller using the provided keys | ||
activities = activity_controller.get_activities(self.activity_keys) | ||
|
||
# check for downstream processes | ||
if any(len(act.upstream()) > 0 for act in activities): | ||
# warning text | ||
text = ("One or more activities have downstream processes. Deleting these activities will remove the " | ||
"exchange from the downstream processes, this can't be undone.\n\nAre you sure you want to " | ||
"continue?") | ||
|
||
# alert the user | ||
choice = QtWidgets.QMessageBox.warning(application.main_window, | ||
"Activity/Activities has/have downstream processes", | ||
text, | ||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, | ||
QtWidgets.QMessageBox.No) | ||
|
||
# return if the user cancels | ||
if choice == QtWidgets.QMessageBox.No: return | ||
|
||
# use the activity controller to delete multiple activities | ||
activity_controller.delete_activities(self.activity_keys) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from typing import Union, Callable, List | ||
|
||
from PySide2 import QtCore | ||
|
||
from activity_browser import activity_controller | ||
from activity_browser.ui.icons import qicons | ||
from activity_browser.actions.base import ABAction | ||
|
||
|
||
class ActivityDuplicate(ABAction): | ||
""" | ||
Duplicate one or multiple activities using their keys. Proxy action to call the controller. | ||
""" | ||
icon = qicons.copy | ||
title = 'Duplicate ***' | ||
activity_keys: List[tuple] | ||
|
||
def __init__(self, activity_keys: Union[List[tuple], Callable], parent: QtCore.QObject): | ||
super().__init__(parent, activity_keys=activity_keys) | ||
|
||
def onTrigger(self, toggled): | ||
activity_controller.duplicate_activities(self.activity_keys) | ||
|
||
|
56 changes: 56 additions & 0 deletions
56
activity_browser/actions/activity/activity_duplicate_to_db.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
from typing import Union, Callable, List | ||
|
||
from PySide2 import QtWidgets, QtCore | ||
|
||
from activity_browser import application, project_settings, activity_controller | ||
from activity_browser.ui.icons import qicons | ||
from activity_browser.actions.base import ABAction | ||
|
||
|
||
class ActivityDuplicateToDB(ABAction): | ||
""" | ||
ABAction to duplicate an activity to another database. Asks the user to what database they want to copy the activity | ||
to, returns if there are no valid databases or when the user cancels. Otherwise uses the activity controller to | ||
duplicate the activities to the chosen database. | ||
""" | ||
icon = qicons.duplicate_to_other_database | ||
title = 'Duplicate to other database' | ||
activity_keys: List[tuple] | ||
|
||
def __init__(self, activity_keys: Union[List[tuple], Callable], parent: QtCore.QObject): | ||
super().__init__(parent, activity_keys=activity_keys) | ||
|
||
def onTrigger(self, toggled): | ||
# get bw activity objects from keys | ||
activities = activity_controller.get_activities(self.activity_keys) | ||
|
||
# get valid databases (not the original database, or locked databases) | ||
origin_db = next(iter(activities)).get("database") | ||
target_dbs = [db for db in project_settings.get_editable_databases() if db != origin_db] | ||
|
||
# return if there are no valid databases to duplicate to | ||
if not target_dbs: | ||
QtWidgets.QMessageBox.warning( | ||
application.main_window, | ||
"No target database", | ||
"No valid target databases available. Create a new database or set one to writable (not read-only)." | ||
) | ||
return | ||
|
||
# construct a dialog where the user can choose a database to duplicate to | ||
target_db, ok = QtWidgets.QInputDialog.getItem( | ||
application.main_window, | ||
"Copy activity to database", | ||
"Target database:", | ||
target_dbs, | ||
0, | ||
False | ||
) | ||
|
||
# return if the user didn't choose, or canceled | ||
if not target_db or not ok: return | ||
|
||
# otherwise move all supplied activities to the db using the controller | ||
for activity in activities: | ||
activity_controller.duplicate_activity_to_db(target_db, activity) | ||
|
170 changes: 170 additions & 0 deletions
170
activity_browser/actions/activity/activity_duplicate_to_loc.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
from typing import Union, Callable, Optional | ||
|
||
import pandas as pd | ||
import brightway2 as bw | ||
from PySide2 import QtCore | ||
|
||
from activity_browser import signals, application, activity_controller, exchange_controller | ||
from activity_browser.bwutils import AB_metadata | ||
from activity_browser.ui.icons import qicons | ||
from activity_browser.actions.base import ABAction | ||
from ...ui.widgets import LocationLinkingDialog | ||
|
||
|
||
class ActivityDuplicateToLoc(ABAction): | ||
""" | ||
ABAction to duplicate an activity and possibly their exchanges to a new location. | ||
""" | ||
icon = qicons.copy | ||
title = 'Duplicate activity to new location' | ||
activity_key: tuple | ||
db_name: str | ||
|
||
def __init__(self, activity_key: Union[tuple, Callable], parent: QtCore.QObject): | ||
super().__init__(parent, activity_key=activity_key) | ||
|
||
def onTrigger(self, toggled): | ||
act = activity_controller.get_activities(self.activity_key)[0] | ||
self.db_name = act.key[0] | ||
|
||
# get list of dependent databases for activity and load to MetaDataStore | ||
databases = [] | ||
for exchange in act.technosphere(): | ||
databases.append(exchange.input[0]) | ||
if self.db_name not in databases: # add own database if it wasn't added already | ||
databases.append(self.db_name) | ||
|
||
# load all dependent databases to MetaDataStore | ||
dbs = {db: AB_metadata.get_database_metadata(db) for db in databases} | ||
# get list of all unique locations in the dependent databases (sorted alphabetically) | ||
locations = [] | ||
for db in dbs.values(): | ||
locations += db['location'].to_list() # add all locations to one list | ||
locations = list(set(locations)) # reduce the list to only unique items | ||
locations.sort() | ||
|
||
# get the location to relink | ||
db = dbs[self.db_name] | ||
old_location = db.loc[db['key'] == act.key]['location'].iloc[0] | ||
|
||
# trigger dialog with autocomplete-writeable-dropdown-list | ||
options = (old_location, locations) | ||
dialog = LocationLinkingDialog.relink_location(act['name'], options, application.main_window) | ||
|
||
if dialog.exec_() != LocationLinkingDialog.Accepted: return | ||
|
||
# read the data from the dialog | ||
for old, new in dialog.relink.items(): | ||
alternatives = [] | ||
new_location = new | ||
if dialog.use_rer.isChecked(): # RER | ||
alternatives.append(dialog.use_rer.text()) | ||
if dialog.use_ews.isChecked(): # Europe without Switzerland | ||
alternatives.append(dialog.use_ews.text()) | ||
if dialog.use_row.isChecked(): # RoW | ||
alternatives.append(dialog.use_row.text()) | ||
# the order we add alternatives is important, they are checked in this order! | ||
if len(alternatives) > 0: | ||
use_alternatives = True | ||
else: | ||
use_alternatives = False | ||
|
||
successful_links = {} # dict of dicts, key of new exch : {new values} <-- see 'values' below | ||
# in the future, 'alternatives' could be improved by making use of some location hierarchy. From that we could | ||
# get things like if the new location is NL but there is no NL, but RER exists, we use that. However, for that | ||
# we need some hierarchical structure to the location data, which may be available from ecoinvent, but we need | ||
# to look for that. | ||
|
||
# get exchanges that we want to relink | ||
for exch in act.technosphere(): | ||
candidate = self.find_candidate(dbs, exch, old_location, new_location, use_alternatives, alternatives) | ||
if candidate is None: | ||
continue # no suitable candidate was found, try the next exchange | ||
|
||
# at this point, we have found 1 suitable candidate, whether that is new_location or alternative location | ||
values = { | ||
'amount': exch.get('amount', False), | ||
'comment': exch.get('comment', False), | ||
'formula': exch.get('formula', False), | ||
'uncertainty': exch.get('uncertainty', False) | ||
} | ||
successful_links[candidate['key'].iloc[0]] = values | ||
|
||
# now, create a new activity by copying the old one | ||
new_code = activity_controller.generate_copy_code(act.key) | ||
new_act = act.copy(new_code) | ||
# update production exchanges | ||
for exc in new_act.production(): | ||
if exc.input.key == act.key: | ||
exc.input = new_act | ||
exc.save() | ||
# update 'products' | ||
for product in new_act.get('products', []): | ||
if product.get('input') == act.key: | ||
product.input = new_act.key | ||
new_act.save() | ||
# save the new location to the activity | ||
activity_controller.modify_activity(new_act.key, 'location', new_location) | ||
|
||
# get exchanges that we want to delete | ||
del_exch = [] # delete these exchanges | ||
for exch in new_act.technosphere(): | ||
candidate = self.find_candidate(dbs, exch, old_location, new_location, use_alternatives, alternatives) | ||
if candidate is None: | ||
continue # no suitable candidate was found, try the next exchange | ||
del_exch.append(exch) | ||
# delete exchanges with old locations | ||
exchange_controller.delete_exchanges(del_exch) | ||
|
||
# add the new exchanges with all values carried over from last exchange | ||
exchange_controller.add_exchanges(list(successful_links.keys()), new_act.key, successful_links) | ||
|
||
# update the MetaDataStore and open new activity | ||
AB_metadata.update_metadata(new_act.key) | ||
signals.safe_open_activity_tab.emit(new_act.key) | ||
|
||
# send signals to relevant locations | ||
bw.databases.set_modified(self.db_name) | ||
signals.database_changed.emit(self.db_name) | ||
signals.databases_changed.emit() | ||
|
||
def find_candidate(self, dbs, exch, old_location, new_location, use_alternatives, alternatives) -> Optional[object]: | ||
"""Find a candidate to replace the exchange with.""" | ||
current_db = exch.input[0] | ||
if current_db == self.db_name: | ||
db = dbs[current_db] | ||
else: # if the exchange is not from the current database, also check the current | ||
# (user may have added their own alternative dependents already) | ||
db = pd.concat([dbs[current_db], dbs[self.db_name]]) | ||
|
||
if db.loc[db['key'] == exch.input]['location'].iloc[0] != old_location: | ||
return # this exchange has a location we're not trying to re-link | ||
|
||
# get relevant data to match on | ||
row = db.loc[db['key'] == exch.input] | ||
name = row['name'].iloc[0] | ||
prod = row['reference product'].iloc[0] | ||
unit = row['unit'].iloc[0] | ||
|
||
# get candidates to match (must have same name, product and unit) | ||
candidates = db.loc[(db['name'] == name) | ||
& (db['reference product'] == prod) | ||
& (db['unit'] == unit)] | ||
if len(candidates) <= 1: | ||
return # this activity does not exist in this database with another location (1 is self) | ||
|
||
# check candidates for new_location | ||
candidate = candidates.loc[candidates['location'] == new_location] | ||
if len(candidate) == 0 and not use_alternatives: | ||
return # there is no candidate | ||
elif len(candidate) > 1: | ||
return # there is more than one candidate, we can't know what to use | ||
elif len(candidate) == 0: | ||
# there are no candidates, but we can try alternatives | ||
for alt in alternatives: | ||
candidate = candidates.loc[candidates['location'] == alt] | ||
if len(candidate) == 1: | ||
break # found an alternative in with this alternative location, stop looking | ||
if len(candidate) != 1: | ||
return # there are either no or multiple matches with alternative locations | ||
return candidate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from typing import Union, Callable, List | ||
|
||
from PySide2 import QtCore | ||
|
||
from activity_browser import signals | ||
from activity_browser.actions.base import ABAction | ||
from activity_browser.ui.icons import qicons | ||
|
||
|
||
class ActivityGraph(ABAction): | ||
""" | ||
ABAction to open one or multiple activities in the graph explorer | ||
""" | ||
icon = qicons.graph_explorer | ||
title = "'Open *** in Graph Explorer'" | ||
activity_keys: List[tuple] | ||
|
||
def __init__(self, activity_keys: Union[List[tuple], Callable], parent: QtCore.QObject): | ||
super().__init__(parent, activity_keys=activity_keys) | ||
|
||
def onTrigger(self, toggled): | ||
for key in self.activity_keys: | ||
signals.open_activity_graph_tab.emit(key) |
Oops, something went wrong.