Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
434 changes: 157 additions & 277 deletions LICENSE.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions appabuild/database/serialized_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ class can use SerializedActivity objects to create Brightway compatible datasets
"Free text for any context information about the dataset."
include_in_tree: Optional[bool] = False
"If True, activity will become a node in built ImpactModel."
properties: Optional[Dict[str, Union[str, float, bool]]] = {}
"""Properties will remain on impact model, and can be used by apparun to breakdown
the results according to life cycle phase, for exemple. Properties can be key/value
(ex: {"phase": "production"} or flags (ex: {production_phase: True})."""

@property
def code(self):
Expand Down
6 changes: 6 additions & 0 deletions appabuild/database/user_database_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ class Activity(DatabaseElement):
"If True, activity will become a node in built ImpactModel."
parameters: Optional[List[str]] = []
"Optional list of parameters necessary to execute this dataset."
properties: Optional[Dict[str, Union[str, float, bool]]] = {}
"""Properties will remain on impact model, and can be used by apparun to breakdown
the results according to life cycle phase, for exemple. Properties can be key/value
(ex: {"phase": "production"} or flags (ex: {production_phase: True})."""

def to_bw_format(self) -> Tuple[Tuple[str, str], dict]:
"""
Expand All @@ -330,6 +334,7 @@ def to_bw_format(self) -> Tuple[Tuple[str, str], dict]:
"amount": self.amount,
"include_in_tree": self.include_in_tree,
"exchanges": [exchange.to_bw_format() for exchange in self.exchanges],
"properties": self.properties,
}

@classmethod
Expand Down Expand Up @@ -413,6 +418,7 @@ def from_serialized_activity(
parameters=serialized_activity.parameters,
include_in_tree=serialized_activity.include_in_tree,
exchanges=[],
properties=serialized_activity.properties,
context=context,
)
context.activities.append(new_activity)
Expand Down
68 changes: 45 additions & 23 deletions appabuild/model/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@
import os
import types
from collections import OrderedDict
from typing import List, Tuple, Optional
from typing import List, Optional, Tuple

import brightway2 as bw
import lca_algebraic as lcaa
import yaml
from apparun.impact_methods import MethodFullName
from apparun.impact_model import ImpactModel, ModelMetadata
from apparun.impact_tree import ImpactTreeNode
from apparun.parameters import (
EnumParam,
FloatParam,
ImpactModelParams,
)
from apparun.parameters import EnumParam, FloatParam, ImpactModelParams
from apparun.tree_node import NodeProperties
from bw2data.backends.peewee import Activity
from lca_algebraic import ActivityExtended, with_db_context
from lca_algebraic.base_utils import _getAmountOrFormula, _getDb, debug
Expand All @@ -33,7 +30,7 @@
from lca_algebraic.params import _fixed_params, newEnumParam, newFloatParam
from sympy import Expr, simplify, symbols

from appabuild.database.databases import parameters_registry, ForegroundDatabase
from appabuild.database.databases import ForegroundDatabase, parameters_registry
from appabuild.exceptions import BwDatabaseError, BwMethodError

act_symbols = {} # Cache of act = > symbol
Expand Down Expand Up @@ -62,9 +59,16 @@ class ImpactModelBuilder:
Main purpose of this class is to build Impact Models.
"""

def __init__(self, user_database_name: str, functional_unit: str, methods: list[str], output_path: str,
metadata: Optional[ModelMetadata] = ModelMetadata(), compile_models: bool = True,
parameters: Optional[dict] = None):
def __init__(
self,
user_database_name: str,
functional_unit: str,
methods: list[str],
output_path: str,
metadata: Optional[ModelMetadata] = ModelMetadata(),
compile_models: bool = True,
parameters: Optional[dict] = None,
):
"""
Initialize the model builder
:param user_database_name: name of the user database (foreground database)
Expand Down Expand Up @@ -103,27 +107,33 @@ def from_yaml(lca_config_path: str) -> ImpactModelBuilder:
lca_config["scope"]["methods"],
os.path.join(
lca_config["outputs"]["model"]["path"],
f"{lca_config['outputs']['model']['name']}.yaml"
f"{lca_config['outputs']['model']['name']}.yaml",
),
lca_config["outputs"]["model"]["metadata"],
lca_config["outputs"]["model"]["compile"],
lca_config["outputs"]["model"]["parameters"]
lca_config["outputs"]["model"]["parameters"],
)
return builder

def build_impact_model(self, foreground_database:Optional[ForegroundDatabase] = None) -> ImpactModel:
def build_impact_model(
self, foreground_database: Optional[ForegroundDatabase] = None
) -> ImpactModel:
"""
Build an Impact Model, the model is a represented as a tree with the functional unit as its root
:param foreground_database: database containing the functional unit
:return: built impact model.
"""

if foreground_database is not None:
foreground_database.set_functional_unit(self.functional_unit, self.parameters)
foreground_database.set_functional_unit(
self.functional_unit, self.parameters
)
foreground_database.execute_at_build_time()

functional_unit_bw = self.find_functional_unit_in_bw()
tree, params = self.build_impact_tree_and_parameters(functional_unit_bw, self.methods)
tree, params = self.build_impact_tree_and_parameters(
functional_unit_bw, self.methods
)
impact_model = ImpactModel(tree=tree, parameters=params, metadata=self.metadata)
return impact_model

Expand All @@ -132,9 +142,13 @@ def find_functional_unit_in_bw(self) -> ActivityExtended:
Find the bw activity matching the functional unit in the bw database. A single activity
should be found as it is to be used as the root of the tree.
"""
functional_unit_bw = [i for i in self.bw_user_database if self.functional_unit == i["name"]]
functional_unit_bw = [
i for i in self.bw_user_database if self.functional_unit == i["name"]
]
if len(functional_unit_bw) < 1:
raise BwDatabaseError(f"Cannot find activity {self.functional_unit} for FU.")
raise BwDatabaseError(
f"Cannot find activity {self.functional_unit} for FU."
)
if len(functional_unit_bw) > 1:
raise BwDatabaseError(
f"Too many activities matching {self.functional_unit} for FU: "
Expand All @@ -144,7 +158,7 @@ def find_functional_unit_in_bw(self) -> ActivityExtended:
return functional_unit_bw

def build_impact_tree_and_parameters(
self, functional_unit_bw: ActivityExtended, methods: List[str]
self, functional_unit_bw: ActivityExtended, methods: List[str]
) -> Tuple[ImpactTreeNode, ImpactModelParams]:
"""
Perform LCA, construct all arithmetic models and collect used parameters.
Expand All @@ -154,7 +168,11 @@ def build_impact_tree_and_parameters(
:return: root node (corresponding to the reference flow) and used parameters.
"""
methods_bw = [to_bw_method(MethodFullName[method]) for method in methods]
tree = ImpactTreeNode(name=functional_unit_bw["name"], amount=1)
tree = ImpactTreeNode(
name=functional_unit_bw["name"],
amount=1,
properties=NodeProperties.from_dict(functional_unit_bw["properties"]),
)
# print("computing model to expression for %s" % model)
self.actToExpression(functional_unit_bw, tree)

Expand Down Expand Up @@ -190,8 +208,8 @@ def build_impact_tree_and_parameters(
[
elem.name
for elem in known_parameters.find_corresponding_parameter(
activity_symbol, must_find_one=False
)
activity_symbol, must_find_one=False
)
]
for activity_symbol in activity_symbols
]
Expand Down Expand Up @@ -327,7 +345,11 @@ def rec_func(act: Activity, impact_model_tree_node: ImpactTreeNode):
ImpactModelBuilder.actToExpression(
sub_act,
impact_model_tree_node.new_child(
name=sub_act["name"], amount=amount
name=sub_act["name"],
amount=amount,
properties=NodeProperties.from_dict(
sub_act["properties"]
),
),
)
amount = 1 # amount is already handled in tree node
Expand All @@ -339,7 +361,7 @@ def rec_func(act: Activity, impact_model_tree_node: ImpactTreeNode):
avoidedBurden = 1

if exch.get("type") == "production" and not exch.get(
"input"
"input"
) == exch.get("output"):
debug("Avoided burden", exch[lcaa.helpers.name])
avoidedBurden = -1
Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ packages = ["app", "appabuild"]

[project]
name = "appabuild"
version = "0.3.1"
version = "0.3.2"
authors = [{ name = "Maxime Peralta", email = "maxime.peralta@cea.fr"}]
maintainers= [{name = "Maxime Peralta", email = "maxime.peralta@cea.fr"}]
description = "Appa Build is a package to build impact models"
keywords = ["ecodesign", "life cycle assessment"]
license = { file = "LICENSE.md" }
readme = "README.md"
classifiers = [
"License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0)",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Intended Audience :: Developers",
Expand Down Expand Up @@ -46,7 +45,7 @@ dependencies = [
"kaleido",
"tqdm",
"ruamel.yaml",
"apparun==0.3.1",
"apparun==0.3.2",
"pre-commit",
"typer==0.15.1",
]
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ aenum
kaleido
tqdm
ruamel.yaml
apparun==0.3.0
apparun==0.3.2
pre-commit
hatchling
Loading