diff --git a/appabuild/config/lca.py b/appabuild/config/lca.py index d940aae..5a3b4b8 100644 --- a/appabuild/config/lca.py +++ b/appabuild/config/lca.py @@ -99,20 +99,6 @@ def parse_parameters(cls, parameters: List[dict]) -> List[ImpactModelParam]: return parsed_parameters - @field_validator("parameters", mode="after") - @classmethod - def eval_exprs(cls, parameters: List[ImpactModelParam]) -> List[ImpactModelParam]: - exprs_set = ParamsValuesSet.build( - {param.name: param.default for param in parameters}, - ImpactModelParams.from_list(parameters), - ) - - values = exprs_set.evaluate() - for param in parameters: - param.update_default(values[param.name]) - - return parameters - def dump_parameters(self) -> List[dict]: return list(map(lambda p: p.model_dump(), self.parameters)) diff --git a/appabuild/database/databases.py b/appabuild/database/databases.py index aaa0a6a..95b94ec 100644 --- a/appabuild/database/databases.py +++ b/appabuild/database/databases.py @@ -1,6 +1,5 @@ """ Contains classes to import LCI databases in Brightway project. -Initialize parameters_registry object which must be filled before Impact Model build. """ from __future__ import annotations @@ -9,12 +8,11 @@ import json import os import re -from collections import ChainMap -from typing import Dict, List, Optional +from typing import Optional import brightway2 as bw import yaml -from apparun.parameters import ImpactModelParam +from apparun.parameters import ImpactModelParams from lca_algebraic import resetParams, setForeground from lxml.etree import XMLSyntaxError from pydantic_core import ValidationError @@ -25,8 +23,6 @@ from appabuild.exceptions import BwDatabaseError, SerializedDataError from appabuild.logger import log_validation_error, logger -parameters_registry = {} - class Database: """ @@ -219,12 +215,12 @@ def __init__(self, name, path): """ Database.__init__(self, name, path) self.fu_name = "" - self.parameters = {} + self.parameters = None self.context = UserDatabaseContext( serialized_activities=[], activities=[], database=BwDatabase(name=name) ) - def set_functional_unit(self, fu_name: str, parameters: dict): + def set_functional_unit(self, fu_name: str, parameters: ImpactModelParams): self.fu_name = fu_name self.parameters = parameters @@ -278,7 +274,6 @@ def execute_at_startup(self): self.find_activities_on_disk() def execute_at_build_time(self): - self.declare_parameters() self.import_in_project() def import_in_project(self) -> None: @@ -313,15 +308,3 @@ def import_in_project(self) -> None: ] bw_database.write(dict(to_write_activities)) setForeground(self.name) - - def declare_parameters(self) -> None: - """ - Initialize each object's parameter as a ImpactModelParam and store it in - parameters_registry object. parameters_registry is used to build Impact Model's - parameters section. - :return: - """ - for parameter in self.parameters: - parameters_registry[parameter["name"]] = ImpactModelParam.from_dict( - parameter - ) diff --git a/appabuild/exceptions.py b/appabuild/exceptions.py index 5c665e5..564a3e1 100644 --- a/appabuild/exceptions.py +++ b/appabuild/exceptions.py @@ -17,3 +17,9 @@ class SerializedDataError(Exception): """Raised when any problem concerning yaml/json dataset is encountered.""" pass + + +class ParameterError(Exception): + """Raised when any problem concerning impact model parameterization is encountered.""" + + pass diff --git a/appabuild/model/builder.py b/appabuild/model/builder.py index 33ddfb8..6e39d83 100644 --- a/appabuild/model/builder.py +++ b/appabuild/model/builder.py @@ -6,6 +6,7 @@ from __future__ import annotations import itertools +import logging import os import types from collections import OrderedDict @@ -28,13 +29,19 @@ _multiLCAWithCache, _replace_fixed_params, ) -from lca_algebraic.params import _fixed_params, newEnumParam, newFloatParam +from lca_algebraic.params import ( + _fixed_params, + _param_registry, + newEnumParam, + newFloatParam, +) from pydantic import ValidationError from sympy import Expr, simplify, symbols, sympify +from sympy.parsing.sympy_parser import parse_expr from appabuild.config.lca import LCAConfig -from appabuild.database.databases import ForegroundDatabase, parameters_registry -from appabuild.exceptions import BwDatabaseError, BwMethodError +from appabuild.database.databases import ForegroundDatabase +from appabuild.exceptions import BwDatabaseError, BwMethodError, ParameterError from appabuild.logger import logger act_symbols = {} # Cache of act = > symbol @@ -76,7 +83,7 @@ def __init__( output_path: str, metadata: Optional[ModelMetadata] = ModelMetadata(), compile_models: bool = True, - parameters: Optional[dict] = None, + parameters: Optional[ImpactModelParams] = None, ): """ Initialize the model builder @@ -110,20 +117,16 @@ def from_yaml(lca_config_path: str) -> ImpactModelBuilder: lca_config = LCAConfig.from_yaml(lca_config_path) builder = ImpactModelBuilder( - lca_config.scope.fu.database, # lca_config["scope"]["fu"]["database"], - lca_config.scope.fu.name, # lca_config["scope"]["fu"]["name"], - lca_config.scope.methods, # lca_config["scope"]["methods"], - # os.path.join( - # lca_config["outputs"]["model"]["path"], - # f"{lca_config['outputs']['model']['name']}.yaml", - # ), + lca_config.scope.fu.database, + lca_config.scope.fu.name, + lca_config.scope.methods, os.path.join( lca_config.model.path, lca_config.model.name + ".yaml", ), - lca_config.model.metadata, # lca_config["outputs"]["model"]["metadata"], - lca_config.model.compile, # lca_config["outputs"]["model"]["compile"], - lca_config.model.dump_parameters(), # lca_config["outputs"]["model"]["parameters"], + lca_config.model.metadata, + lca_config.model.compile, + ImpactModelParams.from_list(lca_config.model.parameters), ) return builder @@ -183,6 +186,9 @@ def build_impact_tree_and_parameters( method format is Appa Run method keys. :return: root node (corresponding to the reference flow) and used parameters. """ + # lcaa param registry can be populated if a model has already been built + _param_registry().clear() + methods_bw = [to_bw_method(MethodFullName[method]) for method in methods] tree = ImpactTreeNode( name=functional_unit_bw["name"], @@ -192,7 +198,45 @@ def build_impact_tree_and_parameters( # print("computing model to expression for %s" % model) self.actToExpression(functional_unit_bw, tree) - # Find required parameters by inspecting symbols + # Check if each symbol corresponds to a known parameter + + # TODO move that in a FloatParam method + params_in_default = [ + parameter.default + for parameter in self.parameters + if parameter.type == "float" + and ( + isinstance(parameter.default, str) + or isinstance(parameter.default, dict) + ) + ] + while ( + len( + [ + parameter + for parameter in params_in_default + if isinstance(parameter, dict) + ] + ) + > 0 + ): + params_in_default_str = [ + parameter + for parameter in params_in_default + if isinstance(parameter, str) + ] + params_in_default_dict = [ + [value for value in parameter.values()] + for parameter in params_in_default + if isinstance(parameter, dict) + ] + params_in_default = ( + list(itertools.chain.from_iterable(params_in_default_dict)) + + params_in_default_str + ) + params_in_default = [ + parameter for parameter in params_in_default if isinstance(parameter, str) + ] # there can be int params at this point free_symbols = set( list( itertools.chain.from_iterable( @@ -211,19 +255,29 @@ def build_impact_tree_and_parameters( ] ) ) + + [ + str(symb) + for symb in list( + itertools.chain.from_iterable( + [ + parse_expr(params_in_default).free_symbols + for params_in_default in params_in_default + ] + ) + ) + ] ) + activity_symbols = set([str(symb["symbol"]) for _, symb in act_symbols.items()]) expected_parameter_symbols = free_symbols - activity_symbols - known_parameters = ImpactModelParams.from_list(parameters_registry.values()) - forbidden_parameter_names = list( itertools.chain( *[ [ elem.name - for elem in known_parameters.find_corresponding_parameter( + for elem in self.parameters.find_corresponding_parameter( activity_symbol, must_find_one=False ) ] @@ -234,35 +288,39 @@ def build_impact_tree_and_parameters( try: if len(forbidden_parameter_names) > 0: - raise ValueError( + raise ParameterError( f"Parameter names {forbidden_parameter_names} are forbidden as they " f"correspond to background activities." ) - except ValueError: - logger.exception("ValueError") - raise - - used_parameters = [ - known_parameters.find_corresponding_parameter(expected_parameter_symbol) - for expected_parameter_symbol in expected_parameter_symbols - ] - unique_used_parameters = [] - [ - unique_used_parameters.append(i) - for i in used_parameters - if i not in unique_used_parameters - ] - unique_used_parameters = ImpactModelParams.from_list(unique_used_parameters) + except ParameterError as e: + logger.exception(e) + raise ParameterError(e) + for expected_parameter_symbol in expected_parameter_symbols: + try: + self.parameters.find_corresponding_parameter(expected_parameter_symbol) + except ValueError: + e = ( + f"ValueError : {expected_parameter_symbol} is required in the impact" + f" model but is unknown in the config. Please check in the LCA " + f"config." + ) + logger.error(e) + raise ParameterError(e) # Declare used parameters in conf file as a lca_algebraic parameter to enable # model building (will not be used afterwards) - for parameter in unique_used_parameters: + + for parameter in self.parameters: + if parameter.name in _param_registry().keys(): + e = f"Parameter {parameter.name} already in lcaa registry." + logging.error(e) + raise ParameterError(e) if isinstance(parameter, FloatParam): newFloatParam( name=parameter.name, default=parameter.default, save=False, - dbname="", + dbname=self.user_database_name, min=0.0, ) if isinstance(parameter, EnumParam): @@ -270,7 +328,7 @@ def build_impact_tree_and_parameters( name=parameter.name, values=parameter.weights, default=parameter.default, - dbname="", + dbname=self.user_database_name, ) # Create dummy reference to biosphere @@ -299,7 +357,7 @@ def build_impact_tree_and_parameters( } ) node.direct_impacts[method] = model_expr.xreplace(sub) - return tree, unique_used_parameters + return tree, self.parameters @staticmethod @with_db_context diff --git a/pyproject.toml b/pyproject.toml index 0676e19..48184c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ dependencies = [ "kaleido", "tqdm", "ruamel.yaml", - "apparun==0.3.6", + "apparun==0.3.7", "typer==0.15.1", "ipython>=7.6.0,<=8.34.0", "mermaid-py==0.7.1" diff --git a/requirements.txt b/requirements.txt index 6a58470..724a69e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ aenum kaleido tqdm ruamel.yaml -apparun==0.3.6 +apparun==0.3.7 ipython>=7.6.0,<=8.34.0 pre-commit hatchling diff --git a/samples/conf/nvidia_ai_gpu_chip_lca_conf.yaml b/samples/conf/nvidia_ai_gpu_chip_lca_conf.yaml index 0b32752..e5a4ee3 100644 --- a/samples/conf/nvidia_ai_gpu_chip_lca_conf.yaml +++ b/samples/conf/nvidia_ai_gpu_chip_lca_conf.yaml @@ -20,14 +20,17 @@ model: report: link: https://appalca.github.io/ description: "A mock example of Appa LCA's impact model corresponding to a fictive AI chip accelerator based on NVIDIA GPU." - date: 03/11/2023 + date: 07/10/2025 version: "1" license: proprietary - appabuild_version: "0.2" + appabuild_version: "0.3.6" parameters: - name: cuda_core type: float - default: 512 + default: + architecture: + Maxwell: 1344 + Pascal: 1280 min: 256 max: 4096 - name: architecture @@ -44,7 +47,10 @@ model: EU: 1 - name: energy_per_inference type: float - default: 110.0 + default: + architecture: #we model energy by inference using a model of the TDP function of the number of cuda cores. We suppose that TDP power corresponds to running inferences at 90fps. + Maxwell: "0.0878*cuda_core*1000/(90*3600)" + Pascal: "0.0679*cuda_core*1000/(90*3600)" pm_perc: 0.2 - name: lifespan type: float @@ -52,6 +58,6 @@ model: pm: 1 - name: inference_per_day type: float - default: 86400000 + default: "30*3600*8" #30fps 8 hours a day min: 0 max: 86400000 \ No newline at end of file diff --git a/samples/datasets/user_database/nvidia_ai_gpu/nvidia_ai_gpu_chip.yaml b/samples/datasets/user_database/nvidia_ai_gpu/nvidia_ai_gpu_chip.yaml index ead8610..9aa9783 100644 --- a/samples/datasets/user_database/nvidia_ai_gpu/nvidia_ai_gpu_chip.yaml +++ b/samples/datasets/user_database/nvidia_ai_gpu/nvidia_ai_gpu_chip.yaml @@ -7,7 +7,7 @@ parameters: - cuda_core - architecture # Only Maxwell or Pascal supported - usage_location # Only FR or EU supported -- energy_per_inference # In mW +- energy_per_inference # In mWh - inference_per_day - lifespan # in years include_in_tree: True diff --git a/samples/datasets/user_database/nvidia_ai_gpu/nvidia_gpu_die_manufacturing.yaml b/samples/datasets/user_database/nvidia_ai_gpu/nvidia_gpu_die_manufacturing.yaml index 0336144..f5a0715 100644 --- a/samples/datasets/user_database/nvidia_ai_gpu/nvidia_gpu_die_manufacturing.yaml +++ b/samples/datasets/user_database/nvidia_ai_gpu/nvidia_gpu_die_manufacturing.yaml @@ -17,14 +17,14 @@ exchanges: options: - name: Pascal parameters_matching: - defect_density: 0.05 + defect_density: 0.0005 technology_node: 16 # is actually 14 for 2 chips, and 16 for 4 chips. fab_location: TW: 1 area: 0.13184623155305694*cuda_core + 21.707425626610416 - name: Maxwell # also includes Maxwell 2.0 parameters_matching: - defect_density: 0.02 + defect_density: 0.0001 technology_node: 28 fab_location: TW: 1 diff --git a/tests/data/appalca_confs/valids/appalca_conf_lca_confs.yaml b/tests/data/appalca_confs/valids/appalca_conf_lca_confs.yaml new file mode 100644 index 0000000..8ae15b9 --- /dev/null +++ b/tests/data/appalca_confs/valids/appalca_conf_lca_confs.yaml @@ -0,0 +1,5 @@ +project_name: cmd_build_lca_confs_test +databases: + foreground: + name: user_database + path: './tests/data/foreground_datasets/valids/' diff --git a/tests/data/cmd_build/appalca_conf.yaml b/tests/data/cmd_build/appalca_conf.yaml index fdc6e70..01b579c 100644 --- a/tests/data/cmd_build/appalca_conf.yaml +++ b/tests/data/cmd_build/appalca_conf.yaml @@ -1,4 +1,4 @@ -project_name: cmd_build_ete_test2 +project_name: cmd_build_ete_test databases: foreground: name: user_database diff --git a/tests/data/cmd_build/expected_scores.yaml b/tests/data/cmd_build/expected_scores.yaml new file mode 100644 index 0000000..eb1b181 --- /dev/null +++ b/tests/data/cmd_build/expected_scores.yaml @@ -0,0 +1,3 @@ +ai_use_phase: 4.8272749055999995 +nvidia_ai_gpu_chip: 13.988936117453397 +nvidia_gpu_chip_manufacturing: 9.161661211853398 diff --git a/tests/data/cmd_build/foreground_datasets/nvidia_ai_gpu_chip.yaml b/tests/data/cmd_build/foreground_datasets/nvidia_ai_gpu_chip.yaml index ead8610..9aa9783 100644 --- a/tests/data/cmd_build/foreground_datasets/nvidia_ai_gpu_chip.yaml +++ b/tests/data/cmd_build/foreground_datasets/nvidia_ai_gpu_chip.yaml @@ -7,7 +7,7 @@ parameters: - cuda_core - architecture # Only Maxwell or Pascal supported - usage_location # Only FR or EU supported -- energy_per_inference # In mW +- energy_per_inference # In mWh - inference_per_day - lifespan # in years include_in_tree: True diff --git a/tests/data/cmd_build/foreground_datasets/nvidia_gpu_die_manufacturing.yaml b/tests/data/cmd_build/foreground_datasets/nvidia_gpu_die_manufacturing.yaml index 0336144..f5a0715 100644 --- a/tests/data/cmd_build/foreground_datasets/nvidia_gpu_die_manufacturing.yaml +++ b/tests/data/cmd_build/foreground_datasets/nvidia_gpu_die_manufacturing.yaml @@ -17,14 +17,14 @@ exchanges: options: - name: Pascal parameters_matching: - defect_density: 0.05 + defect_density: 0.0005 technology_node: 16 # is actually 14 for 2 chips, and 16 for 4 chips. fab_location: TW: 1 area: 0.13184623155305694*cuda_core + 21.707425626610416 - name: Maxwell # also includes Maxwell 2.0 parameters_matching: - defect_density: 0.02 + defect_density: 0.0001 technology_node: 28 fab_location: TW: 1 diff --git a/tests/data/cmd_build/nvidia_ai_gpu_chip_expected.yaml b/tests/data/cmd_build/nvidia_ai_gpu_chip_expected.yaml index ea33b4d..1d8d74d 100644 --- a/tests/data/cmd_build/nvidia_ai_gpu_chip_expected.yaml +++ b/tests/data/cmd_build/nvidia_ai_gpu_chip_expected.yaml @@ -11,10 +11,10 @@ metadata: link: https://appalca.github.io/ description: A mock example of Appa LCA's impact model corresponding to a fictive AI chip accelerator based on NVIDIA GPU. - date: 03/11/2023 + date: 07/10/2025 version: '1' license: proprietary - appabuild_version: '0.2' + appabuild_version: 0.3.6 parameters: - name: architecture default: Maxwell @@ -23,39 +23,45 @@ parameters: Maxwell: 1.0 Pascal: 1.0 - name: cuda_core - default: 512.0 + default: + architecture: + Maxwell: 1344 + Pascal: 1280 type: float - min: 460.8 - max: 563.2 + min: 256.0 + max: 4096.0 distrib: linear pm: null - pm_perc: 0.1 + pm_perc: null - name: energy_per_inference - default: 0.05 + default: + architecture: + Maxwell: 0.0878*cuda_core*1000/(90*3600) + Pascal: 0.0679*cuda_core*1000/(90*3600) type: float - min: 0.01 - max: 0.1 + min: null + max: null distrib: linear pm: null - pm_perc: null + pm_perc: 0.2 - name: inference_per_day - default: 3600.0 + default: 30*3600*8 type: float - min: 3240.0 - max: 3960.0 + min: 0.0 + max: 86400000.0 distrib: linear pm: null - pm_perc: 0.1 + pm_perc: null - name: lifespan - default: 3.6 + default: 2.0 type: float - min: 3.24 - max: 3.96 + min: null + max: null distrib: linear - pm: null - pm_perc: 0.1 + pm: 1.0 + pm_perc: null - name: usage_location - default: EU + default: FR type: enum weights: FR: 1.0 @@ -64,12 +70,12 @@ tree: name: nvidia_ai_gpu_chip models: EFV3_CLIMATE_CHANGE: 12500.0*architecture_Maxwell*(4.6212599075297227e-9*cuda_core - + 7.37132179656539e-6) + 289.6776199311062*architecture_Maxwell*(0.009702834627645097*cuda_core - + 1)**2/((1 - 0.6773699850611761*exp(-0.003779619385733156*cuda_core))**2*(70685.775/(0.1889809692866578*cuda_core + + 7.37132179656539e-6) + 0.007241940498277656*architecture_Maxwell*(0.009702834627645097*cuda_core + + 1)**2/((1 - 0.9980542072708582*exp(-1.889809692866578e-5*cuda_core))**2*(70685.775/(0.1889809692866578*cuda_core + 19.47688243064738) - 106.7778184271516*sqrt(2)/sqrt(0.009702834627645097*cuda_core + 1))) + 12500.0*architecture_Pascal*(4.6891975579761074e-9*cuda_core + 7.808281424221127e-6) - + 2626.882558417281*architecture_Pascal*(0.0060737847877931227*cuda_core + 1)**2/((1 - - 0.33777635255702983*exp(-0.0065923115776528474*cuda_core))**2*(70685.775/(0.13184623155305694*cuda_core + + 0.26268825584172803*architecture_Pascal*(0.0060737847877931227*cuda_core + + 1)**2/((1 - 0.98920497620445418*exp(-6.5923115776528471e-5*cuda_core))**2*(70685.775/(0.13184623155305694*cuda_core + 21.707425626610416) - 101.14318001667067*sqrt(2)/sqrt(0.0060737847877931227*cuda_core + 1))) + 0.00036525*energy_per_inference*inference_per_day*lifespan*(0.005*usage_location_EU + 0.021*usage_location_FR) @@ -94,32 +100,32 @@ tree: - name: nvidia_gpu_chip_manufacturing models: EFV3_CLIMATE_CHANGE: 12500.0*architecture_Maxwell*(4.6212599075297227e-9*cuda_core - + 7.37132179656539e-6) + 289.6776199311062*architecture_Maxwell*(0.009702834627645097*cuda_core - + 1)**2/((1 - 0.6773699850611761*exp(-0.003779619385733156*cuda_core))**2*(70685.775/(0.1889809692866578*cuda_core + + 7.37132179656539e-6) + 0.007241940498277656*architecture_Maxwell*(0.009702834627645097*cuda_core + + 1)**2/((1 - 0.9980542072708582*exp(-1.889809692866578e-5*cuda_core))**2*(70685.775/(0.1889809692866578*cuda_core + 19.47688243064738) - 106.7778184271516*sqrt(2)/sqrt(0.009702834627645097*cuda_core + 1))) + 12500.0*architecture_Pascal*(4.6891975579761074e-9*cuda_core + 7.808281424221127e-6) - + 2626.882558417281*architecture_Pascal*(0.0060737847877931227*cuda_core + - 1)**2/((1 - 0.33777635255702983*exp(-0.0065923115776528474*cuda_core))**2*(70685.775/(0.13184623155305694*cuda_core + + 0.26268825584172803*architecture_Pascal*(0.0060737847877931227*cuda_core + + 1)**2/((1 - 0.98920497620445418*exp(-6.5923115776528471e-5*cuda_core))**2*(70685.775/(0.13184623155305694*cuda_core + 21.707425626610416) - 101.14318001667067*sqrt(2)/sqrt(0.0060737847877931227*cuda_core + 1))) direct_impacts: EFV3_CLIMATE_CHANGE: 12500.0*architecture_Maxwell*(4.6212599075297227e-9*cuda_core - + 7.37132179656539e-6) + 289.6776199311062*architecture_Maxwell*(0.009702834627645097*cuda_core - + 1)**2/((1 - 0.6773699850611761*exp(-0.003779619385733156*cuda_core))**2*(70685.775/(0.1889809692866578*cuda_core + + 7.37132179656539e-6) + 0.007241940498277656*architecture_Maxwell*(0.009702834627645097*cuda_core + + 1)**2/((1 - 0.9980542072708582*exp(-1.889809692866578e-5*cuda_core))**2*(70685.775/(0.1889809692866578*cuda_core + 19.47688243064738) - 106.7778184271516*sqrt(2)/sqrt(0.009702834627645097*cuda_core + 1))) + 12500.0*architecture_Pascal*(4.6891975579761074e-9*cuda_core + 7.808281424221127e-6) - + 2626.882558417281*architecture_Pascal*(0.0060737847877931227*cuda_core + - 1)**2/((1 - 0.33777635255702983*exp(-0.0065923115776528474*cuda_core))**2*(70685.775/(0.13184623155305694*cuda_core + + 0.26268825584172803*architecture_Pascal*(0.0060737847877931227*cuda_core + + 1)**2/((1 - 0.98920497620445418*exp(-6.5923115776528471e-5*cuda_core))**2*(70685.775/(0.13184623155305694*cuda_core + 21.707425626610416) - 101.14318001667067*sqrt(2)/sqrt(0.0060737847877931227*cuda_core + 1))) scaled_direct_impacts: EFV3_CLIMATE_CHANGE: 12500.0*architecture_Maxwell*(4.6212599075297227e-9*cuda_core - + 7.37132179656539e-6) + 289.6776199311062*architecture_Maxwell*(0.009702834627645097*cuda_core - + 1)**2/((1 - 0.6773699850611761*exp(-0.003779619385733156*cuda_core))**2*(70685.775/(0.1889809692866578*cuda_core + + 7.37132179656539e-6) + 0.007241940498277656*architecture_Maxwell*(0.009702834627645097*cuda_core + + 1)**2/((1 - 0.9980542072708582*exp(-1.889809692866578e-5*cuda_core))**2*(70685.775/(0.1889809692866578*cuda_core + 19.47688243064738) - 106.7778184271516*sqrt(2)/sqrt(0.009702834627645097*cuda_core + 1))) + 12500.0*architecture_Pascal*(4.6891975579761074e-9*cuda_core + 7.808281424221127e-6) - + 2626.882558417281*architecture_Pascal*(0.0060737847877931227*cuda_core + - 1)**2/((1 - 0.33777635255702983*exp(-0.0065923115776528474*cuda_core))**2*(70685.775/(0.13184623155305694*cuda_core + + 0.26268825584172803*architecture_Pascal*(0.0060737847877931227*cuda_core + + 1)**2/((1 - 0.98920497620445418*exp(-6.5923115776528471e-5*cuda_core))**2*(70685.775/(0.13184623155305694*cuda_core + 21.707425626610416) - 101.14318001667067*sqrt(2)/sqrt(0.0060737847877931227*cuda_core + 1))) children: [] diff --git a/tests/data/cmd_build/nvidia_ai_gpu_chip_lca_conf.yaml b/tests/data/cmd_build/nvidia_ai_gpu_chip_lca_conf.yaml index 06e20f8..e5a4ee3 100644 --- a/tests/data/cmd_build/nvidia_ai_gpu_chip_lca_conf.yaml +++ b/tests/data/cmd_build/nvidia_ai_gpu_chip_lca_conf.yaml @@ -20,18 +20,19 @@ model: report: link: https://appalca.github.io/ description: "A mock example of Appa LCA's impact model corresponding to a fictive AI chip accelerator based on NVIDIA GPU." - date: 03/11/2023 + date: 07/10/2025 version: "1" license: proprietary - appabuild_version: "0.2" + appabuild_version: "0.3.6" parameters: - name: cuda_core type: float default: architecture: - Maxwell: 512 - Pascal: 560 - pm_perc: 0.1 + Maxwell: 1344 + Pascal: 1280 + min: 256 + max: 4096 - name: architecture type: enum default: Maxwell @@ -40,20 +41,23 @@ model: Pascal: 1 - name: usage_location type: enum - default: EU + default: FR weights: FR: 1 EU: 1 - name: energy_per_inference type: float - default: 0.05 - min: 0.01 - max: 0.1 - - name: inference_per_day - type: float - default: 3600 - pm_perc: 0.1 + default: + architecture: #we model energy by inference using a model of the TDP function of the number of cuda cores. We suppose that TDP power corresponds to running inferences at 90fps. + Maxwell: "0.0878*cuda_core*1000/(90*3600)" + Pascal: "0.0679*cuda_core*1000/(90*3600)" + pm_perc: 0.2 - name: lifespan type: float - default: inference_per_day / 1000 - pm_perc: 0.1 \ No newline at end of file + default: 2.0 + pm: 1 + - name: inference_per_day + type: float + default: "30*3600*8" #30fps 8 hours a day + min: 0 + max: 86400000 \ No newline at end of file diff --git a/tests/data/foreground_datasets/valids/ai_use_phase.yaml b/tests/data/foreground_datasets/valids/ai_use_phase.yaml new file mode 100644 index 0000000..0bbdfb6 --- /dev/null +++ b/tests/data/foreground_datasets/valids/ai_use_phase.yaml @@ -0,0 +1,19 @@ +name: ai_use_phase +location: RER +type: process +unit: unit +amount: 1 +parameters: +- usage_location # Only FR or RER supported +- inference +- energy_per_inference +include_in_tree: True +comment: "Use phase for AI inference." +exchanges: +- database: user_database + name: electricity + type: technosphere + amount: "inference * energy_per_inference / 1000000" + input: + uuid: "electricity_no_ei" + database: user_database diff --git a/tests/data/foreground_datasets/valids/electricity_no_ei.yaml b/tests/data/foreground_datasets/valids/electricity_no_ei.yaml new file mode 100644 index 0000000..916ef97 --- /dev/null +++ b/tests/data/foreground_datasets/valids/electricity_no_ei.yaml @@ -0,0 +1,25 @@ +name: electricity_no_ei +location: GLO +type: process +unit: kwh +amount: 1 +parameters: +- usage_location # Only FR or RER supported +comment: "Low voltage electricity using 2023 https://www.eea.europa.eu/ data." +exchanges: +- database: user_database + name: electricity + type: technosphere + switch: + name: usage_location + options: + - name: EU + amount: 0.005 + input: + database: impact_proxies + uuid: "('EF v3.0', 'climate change', 'global warming potential (GWP100)')_technosphere_proxy" + - name: FR + amount: 0.021 + input: + database: impact_proxies + uuid: "('EF v3.0', 'climate change', 'global warming potential (GWP100)')_technosphere_proxy" \ No newline at end of file diff --git a/tests/data/lca_confs/invalids/parameter_in_default_missing_in_conf.yaml b/tests/data/lca_confs/invalids/parameter_in_default_missing_in_conf.yaml new file mode 100644 index 0000000..7b75052 --- /dev/null +++ b/tests/data/lca_confs/invalids/parameter_in_default_missing_in_conf.yaml @@ -0,0 +1,50 @@ +scope: + fu: + name: 'ai_use_phase' + database: "user_database" + methods: + - "EFV3_CLIMATE_CHANGE" +model: + path: "." + name: "ai_use_phase" + compile: True + metadata: + author: + name: Maxime PERALTA + organization: CEA + mail: maxime.peralta@cea.fr + reviewer: + name: Mathias TORCASO + organization: CEA + mail: + report: + link: https://appalca.github.io/ + description: "A mock example of Appa LCA's impact model corresponding to a fictive AI chip accelerator based on NVIDIA GPU." + date: 03/11/2023 + version: "1" + license: proprietary + appabuild_version: "0.2" + parameters: + - name: energy_per_inference + type: float + default: 0.05*inference + min: 0.01 + max: 0.1 + - name: inference + type: float + default: + usage_location: + FR: inference_per_day*365.25*lifetime + EU: inference_per_day*365.25*lifetime + pm_perc: 0.1 + - name: usage_location + type: enum + default: EU + weights: + FR: 1 + EU: 1 + - name: lifetime + type: float + default: 2 + min: 0 + max: 3 \ No newline at end of file diff --git a/tests/data/lca_confs/invalids/parameter_missing_in_conf.yaml b/tests/data/lca_confs/invalids/parameter_missing_in_conf.yaml new file mode 100644 index 0000000..0b9d96c --- /dev/null +++ b/tests/data/lca_confs/invalids/parameter_missing_in_conf.yaml @@ -0,0 +1,36 @@ +scope: + fu: + name: 'ai_use_phase' + database: "user_database" + methods: + - "EFV3_CLIMATE_CHANGE" +model: + path: "." + name: "ai_use_phase" + compile: True + metadata: + author: + name: Maxime PERALTA + organization: CEA + mail: maxime.peralta@cea.fr + reviewer: + name: Mathias TORCASO + organization: CEA + mail: + report: + link: https://appalca.github.io/ + description: "A mock example of Appa LCA's impact model corresponding to a fictive AI chip accelerator based on NVIDIA GPU." + date: 03/11/2023 + version: "1" + license: proprietary + appabuild_version: "0.2" + parameters: + - name: energy_per_inference + type: float + default: 0.05 + min: 0.01 + max: 0.1 + - name: inference + type: float + default: "60*60" + pm_perc: 0.1 diff --git a/tests/end_to_end/test_cmd_build.py b/tests/end_to_end/test_cmd_build.py index 30a532e..8979511 100644 --- a/tests/end_to_end/test_cmd_build.py +++ b/tests/end_to_end/test_cmd_build.py @@ -4,7 +4,9 @@ import os +import pytest import yaml +from apparun.impact_model import ImpactModel from typer.testing import CliRunner from appabuild.cli.main import cli_app @@ -19,6 +21,7 @@ def test_build_command(): expected_file = os.path.join( DATA_DIR, "cmd_build", "nvidia_ai_gpu_chip_expected.yaml" ) + expected_scores_file = os.path.join(DATA_DIR, "cmd_build", "expected_scores.yaml") result = runner.invoke( cli_app, @@ -39,6 +42,19 @@ def test_build_command(): with open("nvidia_ai_gpu_chip.yaml", "r") as stream: value = yaml.safe_load(stream) - os.remove("nvidia_ai_gpu_chip.yaml") - assert expected == value, "result file not the same as expected file " + + # Check that the generated impact model can be run by Appa Run + model = ImpactModel.from_yaml("nvidia_ai_gpu_chip.yaml") + scores = model.get_nodes_scores() + scores = { + score.name: score.lcia_scores.scores["EFV3_CLIMATE_CHANGE"][0] + for score in scores + } + + with open(expected_scores_file, "r") as stream: + expected_scores = yaml.safe_load(stream) + + assert scores == pytest.approx(expected_scores) + + os.remove("nvidia_ai_gpu_chip.yaml") diff --git a/tests/functional_no_db/test_lca_conf.py b/tests/functional_no_db/test_lca_conf.py index 2ed2338..257b834 100644 --- a/tests/functional_no_db/test_lca_conf.py +++ b/tests/functional_no_db/test_lca_conf.py @@ -6,7 +6,10 @@ import pytest from pydantic import ValidationError +from appabuild import setup +from appabuild.cli.lca import build from appabuild.config.lca import LCAConfig +from appabuild.exceptions import ParameterError from tests import DATA_DIR @@ -57,6 +60,40 @@ def test_parameter_empty_name(): assert error["loc"] in invalid_params_loc +def test_parameter_missing_in_conf(): + """ + Check that an exception is raised if a parameter is missing in the LCA conf. + """ + appa_conf_file = os.path.join( + DATA_DIR, "appalca_confs", "valids", "appalca_conf_lca_confs.yaml" + ) + lca_conf_file = os.path.join( + DATA_DIR, "lca_confs", "invalids", "parameter_missing_in_conf.yaml" + ) + + foreground_database = setup.initialize(appa_conf_file) + with pytest.raises(ParameterError): + setup.build(lca_conf_file, foreground_database) + os.remove("ai_use_phase.yaml") + + +def test_parameter_in_default_missing_in_conf(): + """ + Check that an exception is raised if a parameter is missing in the LCA conf. + """ + appa_conf_file = os.path.join( + DATA_DIR, "appalca_confs", "valids", "appalca_conf_lca_confs.yaml" + ) + lca_conf_file = os.path.join( + DATA_DIR, "lca_confs", "invalids", "parameter_in_default_missing_in_conf.yaml" + ) + + foreground_database = setup.initialize(appa_conf_file) + with pytest.raises(ParameterError): + setup.build(lca_conf_file, foreground_database) + os.remove("ai_use_phase.yaml") + + def test_valid(): """ Check no exception is raised for a valid LCA configuration.