1
1
# -*- coding: utf-8 -*-
2
2
# BioSTEAM: The Biorefinery Simulation and Techno-Economic Analysis Modules
3
- # Copyright (C) 2020-2023, Yoel Cortes-Pena <[email protected] >,
4
-
3
+ # Copyright (C) 2020-, Yoel Cortes-Pena <[email protected] >,
4
+
5
+ # Sarang Bhagwat <[email protected] >
5
6
#
6
7
# This module implements a filtering feature from the stats module of the QSDsan library:
7
8
# QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems
14
15
from scipy .optimize import shgo , differential_evolution
15
16
import numpy as np
16
17
import pandas as pd
18
+ from pandas import DataFrame , read_excel
19
+ from chaospy import distributions as shape
17
20
from ._metric import Metric
18
21
from ._feature import MockFeature
19
22
from ._utils import var_indices , var_columns , indices_to_multiindex
27
30
from .evaluation_tools import load_default_parameters
28
31
import pickle
29
32
30
- __all__ = ('Model' ,)
33
+ __all__ = ('Model' , 'EasyInputModel' )
31
34
32
35
def replace_nones (values , replacement ):
33
36
for i , j in enumerate (values ):
@@ -1268,4 +1271,128 @@ def _info(self, p, m):
1268
1271
def show (self , p = None , m = None ):
1269
1272
"""Return information on p-parameters and m-metrics."""
1270
1273
print (self ._info (p , m ))
1271
- _ipython_display_ = show
1274
+ _ipython_display_ = show
1275
+
1276
+ #%% Easier input parameter distributions and load statements for Model objects
1277
+
1278
+ def codify (statement ):
1279
+ statement = replace_apostrophes (statement )
1280
+ statement = replace_newline (statement )
1281
+ return statement
1282
+
1283
+ def replace_newline (statement ):
1284
+ statement = statement .replace ('\n ' , ';' )
1285
+ return statement
1286
+
1287
+ def replace_apostrophes (statement ):
1288
+ statement = statement .replace ('’' , "'" ).replace ('‘' , "'" ).replace ('“' , '"' ).replace ('”' , '"' )
1289
+ return statement
1290
+
1291
+ def create_function (code , namespace_dict ):
1292
+ def wrapper_fn (statement ):
1293
+ def f (x ):
1294
+ namespace_dict ['x' ] = x
1295
+ exec (codify (statement ), namespace_dict )
1296
+ return f
1297
+ function = wrapper_fn (code )
1298
+ return function
1299
+
1300
+ class EasyInputModel (Model ):
1301
+ """
1302
+ Create an EasyInputModel object that allows for evaluation over a sample space
1303
+ using the Model class, with input parameter distributions and load functions
1304
+ entered using a spreadsheet file or DataFrame object.
1305
+
1306
+ Parameters
1307
+ ----------
1308
+ system : System
1309
+ Should reflect the model state.
1310
+ metrics : tuple[Metric]
1311
+ Metrics to be evaluated by model.
1312
+ specification=None : Function, optional
1313
+ Loads specifications once all parameters are set. Specification should
1314
+ simulate the system as well.
1315
+ params=None : Iterable[Parameter], optional
1316
+ Parameters to sample from.
1317
+ exception_hook : callable(exception, sample)
1318
+ Function called after a failed evaluation. The exception hook should
1319
+ return either None or metric values given the exception and sample.
1320
+ namespace_dict : dict, optional
1321
+ Dictionary used to update the namespace accessed when executing
1322
+ statements to load values into model parameters.
1323
+
1324
+ """
1325
+ def __init__ (self , system , metrics = None , specification = None ,
1326
+ parameters = None , retry_evaluation = True , exception_hook = 'warn' ,
1327
+ namespace_dict = {}):
1328
+ Model .__init__ (self , system = system , metrics = metrics , specification = specification ,
1329
+ parameters = parameters , retry_evaluation = retry_evaluation , exception_hook = exception_hook )
1330
+ self .namespace_dict = namespace_dict
1331
+ # globals().update(namespace_dict)
1332
+
1333
+ def load_parameter_distributions (self , distributions ,):
1334
+ """
1335
+ Load a list of distributions and statements to load values for user-selected
1336
+ parameters.
1337
+
1338
+ Parameters
1339
+ ----------
1340
+ distributions : pandas.DataFrame or file path to a spreadsheet of the following format:
1341
+ Column titles (these must be included, but others may be added for convenience):
1342
+ 'Parameter name': String
1343
+ Name of the parameter.
1344
+ 'Element': String, optional
1345
+ 'Kind': String, optional
1346
+ 'Units': String, optional
1347
+ 'Baseline': float or int
1348
+ The baseline value of the parameter.
1349
+ 'Shape': String, one of ['Uniform', 'Triangular']
1350
+ The shape of the parameter distribution.
1351
+ 'Lower': float or int
1352
+ The lower value defining the shape of the parameter distribution.
1353
+ 'Midpoint': float or int
1354
+ The midpoint value defining the shape of a 'Triangular' parameter distribution.
1355
+ 'Upper': float or int
1356
+ The upper value defining the shape of the parameter distribution.
1357
+ 'Load Statements': String
1358
+ A statement executed to load the value of the parameter. The value is stored in
1359
+ the variable x. A namespace defined in the namespace_dict during EasyInputModel
1360
+ initialization may be accessed.
1361
+ E.g., to load a value into an example distillation unit D101's light key recovery,
1362
+ ensure 'D101' is a key pointing to the D101 unit object in namespace_dict, then
1363
+ simply include the load statement: 'D101.Lr = x'.
1364
+
1365
+ """
1366
+
1367
+ df = distributions
1368
+ if type (df ) is not DataFrame :
1369
+ df = read_excel (distributions )
1370
+
1371
+ namespace_dict = self .namespace_dict
1372
+ param = self .parameter
1373
+
1374
+ for i , row in df .iterrows ():
1375
+ name = row ['Parameter name' ]
1376
+ element = row ['Element' ] # currently only compatible with String elements
1377
+ kind = row ['Kind' ]
1378
+ units = row ['Units' ]
1379
+ baseline = row ['Baseline' ]
1380
+ shape_data = row ['Shape' ]
1381
+ lower , midpoint , upper = row ['Lower' ], row ['Midpoint' ], row ['Upper' ]
1382
+ load_statements = row ['Load Statements' ]
1383
+
1384
+ D = None
1385
+ if shape_data .lower () in ['triangular' , 'triangle' ,]:
1386
+ D = shape .Triangle (lower , midpoint , upper )
1387
+ elif shape_data .lower () in ['uniform' ,]:
1388
+ if not str (midpoint )== 'nan' :
1389
+ raise ValueError (f"The parameter distribution for { name } ({ element } ) is 'Uniform' but was associated with a given midpoint value." )
1390
+ D = shape .Uniform (lower , upper )
1391
+
1392
+ param (name = name ,
1393
+ setter = create_function (load_statements , namespace_dict ),
1394
+ element = element ,
1395
+ kind = kind ,
1396
+ units = units ,
1397
+ baseline = baseline ,
1398
+ distribution = D )
0 commit comments