66from __future__ import annotations
77
88import itertools
9+ import logging
910import os
1011import types
1112from collections import OrderedDict
2829 _multiLCAWithCache ,
2930 _replace_fixed_params ,
3031)
31- from lca_algebraic .params import _fixed_params , newEnumParam , newFloatParam
32+ from lca_algebraic .params import (
33+ _fixed_params ,
34+ _param_registry ,
35+ newEnumParam ,
36+ newFloatParam ,
37+ )
3238from pydantic import ValidationError
3339from sympy import Expr , simplify , symbols , sympify
40+ from sympy .parsing .sympy_parser import parse_expr
3441
3542from appabuild .config .lca import LCAConfig
36- from appabuild .database .databases import ForegroundDatabase , parameters_registry
37- from appabuild .exceptions import BwDatabaseError , BwMethodError
43+ from appabuild .database .databases import ForegroundDatabase
44+ from appabuild .exceptions import BwDatabaseError , BwMethodError , ParameterError
3845from appabuild .logger import logger
3946
4047act_symbols = {} # Cache of act = > symbol
@@ -76,7 +83,7 @@ def __init__(
7683 output_path : str ,
7784 metadata : Optional [ModelMetadata ] = ModelMetadata (),
7885 compile_models : bool = True ,
79- parameters : Optional [dict ] = None ,
86+ parameters : Optional [ImpactModelParams ] = None ,
8087 ):
8188 """
8289 Initialize the model builder
@@ -110,20 +117,16 @@ def from_yaml(lca_config_path: str) -> ImpactModelBuilder:
110117 lca_config = LCAConfig .from_yaml (lca_config_path )
111118
112119 builder = ImpactModelBuilder (
113- lca_config .scope .fu .database , # lca_config["scope"]["fu"]["database"],
114- lca_config .scope .fu .name , # lca_config["scope"]["fu"]["name"],
115- lca_config .scope .methods , # lca_config["scope"]["methods"],
116- # os.path.join(
117- # lca_config["outputs"]["model"]["path"],
118- # f"{lca_config['outputs']['model']['name']}.yaml",
119- # ),
120+ lca_config .scope .fu .database ,
121+ lca_config .scope .fu .name ,
122+ lca_config .scope .methods ,
120123 os .path .join (
121124 lca_config .model .path ,
122125 lca_config .model .name + ".yaml" ,
123126 ),
124- lca_config .model .metadata , # lca_config["outputs"]["model"]["metadata"],
125- lca_config .model .compile , # lca_config["outputs"]["model"]["compile"],
126- lca_config . model . dump_parameters (), # lca_config["outputs"][" model"][" parameters"] ,
127+ lca_config .model .metadata ,
128+ lca_config .model .compile ,
129+ ImpactModelParams . from_list ( lca_config . model . parameters ) ,
127130 )
128131 return builder
129132
@@ -183,6 +186,9 @@ def build_impact_tree_and_parameters(
183186 method format is Appa Run method keys.
184187 :return: root node (corresponding to the reference flow) and used parameters.
185188 """
189+ # lcaa param registry can be populated if a model has already been built
190+ _param_registry ().clear ()
191+
186192 methods_bw = [to_bw_method (MethodFullName [method ]) for method in methods ]
187193 tree = ImpactTreeNode (
188194 name = functional_unit_bw ["name" ],
@@ -192,7 +198,45 @@ def build_impact_tree_and_parameters(
192198 # print("computing model to expression for %s" % model)
193199 self .actToExpression (functional_unit_bw , tree )
194200
195- # Find required parameters by inspecting symbols
201+ # Check if each symbol corresponds to a known parameter
202+
203+ # TODO move that in a FloatParam method
204+ params_in_default = [
205+ parameter .default
206+ for parameter in self .parameters
207+ if parameter .type == "float"
208+ and (
209+ isinstance (parameter .default , str )
210+ or isinstance (parameter .default , dict )
211+ )
212+ ]
213+ while (
214+ len (
215+ [
216+ parameter
217+ for parameter in params_in_default
218+ if isinstance (parameter , dict )
219+ ]
220+ )
221+ > 0
222+ ):
223+ params_in_default_str = [
224+ parameter
225+ for parameter in params_in_default
226+ if isinstance (parameter , str )
227+ ]
228+ params_in_default_dict = [
229+ [value for value in parameter .values ()]
230+ for parameter in params_in_default
231+ if isinstance (parameter , dict )
232+ ]
233+ params_in_default = (
234+ list (itertools .chain .from_iterable (params_in_default_dict ))
235+ + params_in_default_str
236+ )
237+ params_in_default = [
238+ parameter for parameter in params_in_default if isinstance (parameter , str )
239+ ] # there can be int params at this point
196240 free_symbols = set (
197241 list (
198242 itertools .chain .from_iterable (
@@ -211,19 +255,29 @@ def build_impact_tree_and_parameters(
211255 ]
212256 )
213257 )
258+ + [
259+ str (symb )
260+ for symb in list (
261+ itertools .chain .from_iterable (
262+ [
263+ parse_expr (params_in_default ).free_symbols
264+ for params_in_default in params_in_default
265+ ]
266+ )
267+ )
268+ ]
214269 )
270+
215271 activity_symbols = set ([str (symb ["symbol" ]) for _ , symb in act_symbols .items ()])
216272
217273 expected_parameter_symbols = free_symbols - activity_symbols
218274
219- known_parameters = ImpactModelParams .from_list (parameters_registry .values ())
220-
221275 forbidden_parameter_names = list (
222276 itertools .chain (
223277 * [
224278 [
225279 elem .name
226- for elem in known_parameters .find_corresponding_parameter (
280+ for elem in self . parameters .find_corresponding_parameter (
227281 activity_symbol , must_find_one = False
228282 )
229283 ]
@@ -234,43 +288,47 @@ def build_impact_tree_and_parameters(
234288
235289 try :
236290 if len (forbidden_parameter_names ) > 0 :
237- raise ValueError (
291+ raise ParameterError (
238292 f"Parameter names { forbidden_parameter_names } are forbidden as they "
239293 f"correspond to background activities."
240294 )
241- except ValueError :
242- logger .exception ("ValueError" )
243- raise
244-
245- used_parameters = [
246- known_parameters .find_corresponding_parameter (expected_parameter_symbol )
247- for expected_parameter_symbol in expected_parameter_symbols
248- ]
249- unique_used_parameters = []
250- [
251- unique_used_parameters .append (i )
252- for i in used_parameters
253- if i not in unique_used_parameters
254- ]
255- unique_used_parameters = ImpactModelParams .from_list (unique_used_parameters )
295+ except ParameterError as e :
296+ logger .exception (e )
297+ raise ParameterError (e )
298+ for expected_parameter_symbol in expected_parameter_symbols :
299+ try :
300+ self .parameters .find_corresponding_parameter (expected_parameter_symbol )
301+ except ValueError :
302+ e = (
303+ f"ValueError : { expected_parameter_symbol } is required in the impact"
304+ f" model but is unknown in the config. Please check in the LCA "
305+ f"config."
306+ )
307+ logger .error (e )
308+ raise ParameterError (e )
256309
257310 # Declare used parameters in conf file as a lca_algebraic parameter to enable
258311 # model building (will not be used afterwards)
259- for parameter in unique_used_parameters :
312+
313+ for parameter in self .parameters :
314+ if parameter .name in _param_registry ().keys ():
315+ e = f"Parameter { parameter .name } already in lcaa registry."
316+ logging .error (e )
317+ raise ParameterError (e )
260318 if isinstance (parameter , FloatParam ):
261319 newFloatParam (
262320 name = parameter .name ,
263321 default = parameter .default ,
264322 save = False ,
265- dbname = "" ,
323+ dbname = self . user_database_name ,
266324 min = 0.0 ,
267325 )
268326 if isinstance (parameter , EnumParam ):
269327 newEnumParam (
270328 name = parameter .name ,
271329 values = parameter .weights ,
272330 default = parameter .default ,
273- dbname = "" ,
331+ dbname = self . user_database_name ,
274332 )
275333
276334 # Create dummy reference to biosphere
@@ -299,7 +357,7 @@ def build_impact_tree_and_parameters(
299357 }
300358 )
301359 node .direct_impacts [method ] = model_expr .xreplace (sub )
302- return tree , unique_used_parameters
360+ return tree , self . parameters
303361
304362 @staticmethod
305363 @with_db_context
0 commit comments