Skip to content

Commit cabcda5

Browse files
committed
initial commit
1 parent 142ab1c commit cabcda5

File tree

1 file changed

+131
-4
lines changed

1 file changed

+131
-4
lines changed

biosteam/evaluation/_model.py

+131-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# -*- coding: utf-8 -*-
22
# BioSTEAM: The Biorefinery Simulation and Techno-Economic Analysis Modules
3-
# Copyright (C) 2020-2023, Yoel Cortes-Pena <[email protected]>,
4-
# Yalin Li <[email protected]>
3+
# Copyright (C) 2020-, Yoel Cortes-Pena <[email protected]>,
4+
# Yalin Li <[email protected]>,
5+
# Sarang Bhagwat <[email protected]>
56
#
67
# This module implements a filtering feature from the stats module of the QSDsan library:
78
# QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems
@@ -14,6 +15,8 @@
1415
from scipy.optimize import shgo, differential_evolution
1516
import numpy as np
1617
import pandas as pd
18+
from pandas import DataFrame, read_excel
19+
from chaospy import distributions as shape
1720
from ._metric import Metric
1821
from ._feature import MockFeature
1922
from ._utils import var_indices, var_columns, indices_to_multiindex
@@ -27,7 +30,7 @@
2730
from .evaluation_tools import load_default_parameters
2831
import pickle
2932

30-
__all__ = ('Model',)
33+
__all__ = ('Model', 'EasyInputModel')
3134

3235
def replace_nones(values, replacement):
3336
for i, j in enumerate(values):
@@ -1268,4 +1271,128 @@ def _info(self, p, m):
12681271
def show(self, p=None, m=None):
12691272
"""Return information on p-parameters and m-metrics."""
12701273
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

Comments
 (0)