Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sarangbhagwat committed Oct 31, 2024
1 parent 142ab1c commit cabcda5
Showing 1 changed file with 131 additions and 4 deletions.
135 changes: 131 additions & 4 deletions biosteam/evaluation/_model.py
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
Expand All @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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)

0 comments on commit cabcda5

Please sign in to comment.