-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
142ab1c
commit cabcda5
Showing
1 changed file
with
131 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
# -*- coding: utf-8 -*- | ||
# BioSTEAM: The Biorefinery Simulation and Techno-Economic Analysis Modules | ||
# Copyright (C) 2020-2023, Yoel Cortes-Pena <[email protected]>, | ||
# Yalin Li <[email protected]> | ||
# Copyright (C) 2020-, Yoel Cortes-Pena <[email protected]>, | ||
# Yalin Li <[email protected]>, | ||
# Sarang Bhagwat <[email protected]> | ||
# | ||
# This module implements a filtering feature from the stats module of the QSDsan library: | ||
# QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems | ||
|
@@ -14,6 +15,8 @@ | |
from scipy.optimize import shgo, differential_evolution | ||
import numpy as np | ||
import pandas as pd | ||
from pandas import DataFrame, read_excel | ||
from chaospy import distributions as shape | ||
from ._metric import Metric | ||
from ._feature import MockFeature | ||
from ._utils import var_indices, var_columns, indices_to_multiindex | ||
|
@@ -27,7 +30,7 @@ | |
from .evaluation_tools import load_default_parameters | ||
import pickle | ||
|
||
__all__ = ('Model',) | ||
__all__ = ('Model', 'EasyInputModel') | ||
|
||
def replace_nones(values, replacement): | ||
for i, j in enumerate(values): | ||
|
@@ -1268,4 +1271,128 @@ def _info(self, p, m): | |
def show(self, p=None, m=None): | ||
"""Return information on p-parameters and m-metrics.""" | ||
print(self._info(p, m)) | ||
_ipython_display_ = show | ||
_ipython_display_ = show | ||
|
||
#%% Easier input parameter distributions and load statements for Model objects | ||
|
||
def codify(statement): | ||
statement = replace_apostrophes(statement) | ||
statement = replace_newline(statement) | ||
return statement | ||
|
||
def replace_newline(statement): | ||
statement = statement.replace('\n', ';') | ||
return statement | ||
|
||
def replace_apostrophes(statement): | ||
statement = statement.replace('’', "'").replace('‘', "'").replace('“', '"').replace('”', '"') | ||
return statement | ||
|
||
def create_function(code, namespace_dict): | ||
def wrapper_fn(statement): | ||
def f(x): | ||
namespace_dict['x'] = x | ||
exec(codify(statement), namespace_dict) | ||
return f | ||
function = wrapper_fn(code) | ||
return function | ||
|
||
class EasyInputModel(Model): | ||
""" | ||
Create an EasyInputModel object that allows for evaluation over a sample space | ||
using the Model class, with input parameter distributions and load functions | ||
entered using a spreadsheet file or DataFrame object. | ||
Parameters | ||
---------- | ||
system : System | ||
Should reflect the model state. | ||
metrics : tuple[Metric] | ||
Metrics to be evaluated by model. | ||
specification=None : Function, optional | ||
Loads specifications once all parameters are set. Specification should | ||
simulate the system as well. | ||
params=None : Iterable[Parameter], optional | ||
Parameters to sample from. | ||
exception_hook : callable(exception, sample) | ||
Function called after a failed evaluation. The exception hook should | ||
return either None or metric values given the exception and sample. | ||
namespace_dict : dict, optional | ||
Dictionary used to update the namespace accessed when executing | ||
statements to load values into model parameters. | ||
""" | ||
def __init__(self, system, metrics=None, specification=None, | ||
parameters=None, retry_evaluation=True, exception_hook='warn', | ||
namespace_dict={}): | ||
Model.__init__(self, system=system, metrics=metrics, specification=specification, | ||
parameters=parameters, retry_evaluation=retry_evaluation, exception_hook=exception_hook) | ||
self.namespace_dict = namespace_dict | ||
# globals().update(namespace_dict) | ||
|
||
def load_parameter_distributions(self, distributions,): | ||
""" | ||
Load a list of distributions and statements to load values for user-selected | ||
parameters. | ||
Parameters | ||
---------- | ||
distributions : pandas.DataFrame or file path to a spreadsheet of the following format: | ||
Column titles (these must be included, but others may be added for convenience): | ||
'Parameter name': String | ||
Name of the parameter. | ||
'Element': String, optional | ||
'Kind': String, optional | ||
'Units': String, optional | ||
'Baseline': float or int | ||
The baseline value of the parameter. | ||
'Shape': String, one of ['Uniform', 'Triangular'] | ||
The shape of the parameter distribution. | ||
'Lower': float or int | ||
The lower value defining the shape of the parameter distribution. | ||
'Midpoint': float or int | ||
The midpoint value defining the shape of a 'Triangular' parameter distribution. | ||
'Upper': float or int | ||
The upper value defining the shape of the parameter distribution. | ||
'Load Statements': String | ||
A statement executed to load the value of the parameter. The value is stored in | ||
the variable x. A namespace defined in the namespace_dict during EasyInputModel | ||
initialization may be accessed. | ||
E.g., to load a value into an example distillation unit D101's light key recovery, | ||
ensure 'D101' is a key pointing to the D101 unit object in namespace_dict, then | ||
simply include the load statement: 'D101.Lr = x'. | ||
""" | ||
|
||
df = distributions | ||
if type(df) is not DataFrame: | ||
df = read_excel(distributions) | ||
|
||
namespace_dict = self.namespace_dict | ||
param = self.parameter | ||
|
||
for i, row in df.iterrows(): | ||
name = row['Parameter name'] | ||
element = row['Element'] # currently only compatible with String elements | ||
kind = row['Kind'] | ||
units = row['Units'] | ||
baseline = row['Baseline'] | ||
shape_data = row['Shape'] | ||
lower, midpoint, upper = row['Lower'], row['Midpoint'], row['Upper'] | ||
load_statements = row['Load Statements'] | ||
|
||
D = None | ||
if shape_data.lower() in ['triangular', 'triangle',]: | ||
D = shape.Triangle(lower, midpoint, upper) | ||
elif shape_data.lower() in ['uniform',]: | ||
if not str(midpoint)=='nan': | ||
raise ValueError(f"The parameter distribution for {name} ({element}) is 'Uniform' but was associated with a given midpoint value.") | ||
D = shape.Uniform(lower, upper) | ||
|
||
param(name=name, | ||
setter=create_function(load_statements, namespace_dict), | ||
element=element, | ||
kind=kind, | ||
units=units, | ||
baseline=baseline, | ||
distribution=D) |